Everything else in these docs produces files: you build a site, the pages land on disk, you deploy them. This is the other mode. sitekit serve turns the engine into an HTTP service that generates HTML per request, bound to a playbook. Same voice, same style, same shapes, same verify — but the output is a response body instead of a file. A page can be generated on demand for a visitor, or a single section of an otherwise static page can be filled in at runtime.
The brand guarantee is unchanged. A response is composed from the same voice pack, style pack, and shape as a built page, and runs through verify before it’s returned. Dynamic doesn’t mean unaligned.

Start the engine

sitekit serve <playbook>
The playbook is required — it’s what binds every response to a composition.
sitekit serve default --port 8080
sitekit serve default --llm anthropic:claude-sonnet-4-6 --cache-responses 200
OptionDefaultNotes
--port <n>8080HTTP port.
--host <host>127.0.0.1Loopback by default.
--llm <provider:model>anthropic:claude-sonnet-4-6The model behind generation.
--cache-responses <n>0 (off)In-memory LRU keyed by playbook + prompt hash.
--max-tokens <n>8192Per-response output cap.
--temperature <t>0.7Sampling temperature.
--timeout-ms <n>60000Wall-clock per request.
--strictoffTreat verify warnings as errors.
--no-verifySkip the pre-flight verify. Development only.
--json-logsoffEmit logs as JSON lines.
--host 0.0.0.0 exposes the engine on all interfaces with no authentication. There is no auth layer in the engine — that is deliberately the consumer’s job. If you bind it publicly, put your own auth, rate limiting, and quota in front of it.

Generate a whole page

POST /generate
Content-Type: application/json

{ "prompt": "freelance time tracking app" }
Streams Server-Sent Events by default. Set Accept: text/html for a buffered document, or Accept: application/json for { html, meta }. A full page is a big generation — expect it to be slow in model terms. Which is why the interesting endpoint is the next one.

Generate one section

POST /generate/element/hero
Content-Type: application/json

{
  "prompt": "freelance time tracking app",
  "content": {
    "headline": "Bill every hour you actually worked",
    "cta": { "primary": { "label": "Try free", "href": "/signup" } }
  },
  "pageContext": { "topic": "freelance time tracking", "siblingElements": ["hero", "features", "cta"] }
}
This returns one fragment — a <section>, <header>, <footer>, <form> — ready to drop into the DOM of a page you already rendered.
FieldRequiredNotes
promptone of prompt / contentFree-text instruction, capped at 64 KB. Alone, the model derives the content from it.
contentone of prompt / contentTyped content matching the element’s contentSchema. Validated server-side — E_SCHEMA_INVALID_CONTENT on failure.
pageContextoptionalCoherence hints (topic, title, siblingElements) so parallel elements agree with each other.
streamoptionaltrue, or Accept: text/event-stream, for SSE.

Why sections rather than pages

Three reasons, all measured:
  • Latency. A full page is on the order of a couple of minutes on a frontier model. One element is roughly 800 ms to 3 s.
  • Wasted tokens. Around 3K tokens per page call go to re-emitting the canonical stylesheet that’s already on the page. The per-element prompt skips it — the parent document already has the CSS.
  • Zero-cost elements. An element that doesn’t need personalizing (a footer, a social-proof strip) can ship a static.html and return in 1–3 ms with no model call at all.
pageContext.siblingElements is what stops four independently generated sections from reading like four different products. Pass it whenever you fan out.

Generate several sections at once

The page endpoint can multiplex. Supply fanout or fanoutShape and it switches into parallel mode and always responds as SSE.
POST /generate
Content-Type: application/json

# Form A — an explicit ordered list (1–10 elements)
{ "fanout": ["hero", "features", "proof", "cta"], "prompt": "freelance time tracking" }

# Form B — driven by a page shape's composes[]
{ "fanoutShape": "landing", "prompt": "freelance time tracking" }
The engine launches N parallel calls (capped by maxConcurrency, default 5) and pipes each completion into one SSE stream as it finishes. Fast elements arrive first. Time-to-first-element is one element-latency, not the whole page’s.
event: fanout-start
data: {"elements":["hero","features","proof","cta"],"fanoutShape":"landing"}

event: element-start
data: {"element":"hero","index":0}

event: element-done
data: {"element":"hero","index":0,"html":"<section data-element=…>…</section>",
       "verify":"pass","cache":"miss","llm":"anthropic:claude-sonnet-4-6","latencyMs":1240}

event: element-error
data: {"element":"features","index":1,"code":"E_FRAGMENT_MALFORMED",
       "message":"no-style assertion failed"}
By default a failed element is non-fatal — its siblings continue and you render what arrived. Pass strict: true to abort every sibling on the first failure. Per-element content overrides go in content keyed by element name.

Every response says what happened

HeaderValue
X-Sitekit-ElementThe element name.
X-Sitekit-Compositionvoice=<v>;style=<s>.
X-Sitekit-LLM<provider>:<model>, or literally static when static.html short-circuited.
X-Sitekit-Verifypass / warn / fail / skipped.
X-Sitekit-Cachehit / miss / bypassed.
X-Sitekit-Latency-MsWall-clock for the request.
Cache-ControlAlways no-store.
X-Sitekit-Verify is the one to watch in production — it tells you whether the fragment you just received passed its brand and structure checks.

The rest of the surface

EndpointPurpose
GET /A bundled UI — prompt box and result viewer. Good for trying the engine.
GET /elementsWhich element shapes this playbook can render.
GET /shapes/:nameA shape definition, including its composes[] — use it to drive a fan-out.
GET /compositionThe playbook composition currently bound, as JSON.
POST /edit-regionSurgical single-section edit of a page you submit.
GET /healthzLiveness.
GET /readyzReadiness — only true once the model boot probe has succeeded.
GET /demoA worked demonstration page.
Use /readyz rather than /healthz as your load-balancer gate. The process answers /healthz before it has proven it can reach the model.

Caching

The engine holds the invariants at boot — the composed system prompt, the pack’s style.css, the fonts URL, the marker template — for the process lifetime. --cache-responses <n> adds an optional in-memory LRU keyed by (playbook, prompt-sha). It’s off by default. Richer caching — semantic similarity, edge KV, pre-rendered tranches — is deliberately left to the consumer; the engine stays simple.

Generating a fragment without a server

If you want one section as a file rather than a response, the build verb does it directly:
sitekit run --element=hero --slug=hero-a --topic="freelance time tracking"
Same composition, same checks, written to disk. That’s the right tool for a fragment you’ll commit — including authoring experiment arms. Use serve when the fragment has to differ per visitor.

Where this fits

Static build and dynamic generation aren’t alternatives; most real deployments use both. Build the site the ordinary way, then let one region come from the engine — a hero that changes by campaign, a proof strip that reflects the visitor’s industry — while the rest stays a plain, fast, cacheable file.

Shapes

Element shapes are what these endpoints render.

Experiments

Split-test a region — including a section that keeps regenerating.