API and raw mode
JSON endpoints, content fragments, and query string variables.
Raw mode
Setting raw: true in front matter outputs the converted Markdown body
without the view template wrapper. TT variables still resolve. Useful for
content fragments, AJAX partials, or HTML snippets fetched by other pages.
---
title: Fragment
raw: true
---
Content here - no html, head, or layout wrapper.
[% version %] resolves normally.
The default content type for raw mode is text/plain; charset=utf-8.
API mode
Setting api: true skips the Markdown pipeline entirely and treats the
page body as a pure TT template. No Markdown conversion, no layout wrapper.
Default content type is application/json; charset=utf-8.
Useful for lightweight JSON endpoints that aggregate remote data:
---
title: Status API
api: true
---
{
"status": "ok",
"site": "[% site_name %]",
"version": "[% version %]"
}
The output is trimmed of leading and trailing whitespace for clean JSON.
Content type
Override the default content type with content_type: front matter. Works
with both raw: true and api: true:
---
api: true
content_type: text/csv; charset=utf-8
---
name,value
site,[% site_name %]
Query string variables
Pages that declare query_params: in front matter can access URL query
string values as [% query.param_name %] in TT. Only declared parameters
are accessible - undeclared parameters are silently ignored.
---
title: Search
query_params:
- q
- page
---
[% IF query.q %]
Results for: [% query.q %]
[% ELSE %]
Enter a search term.
[% END %]
Requests with matching query parameters bypass the cache - the page always
regenerates. Requests without matching parameters (or to pages without
query_params:) use the normal cache path.
Query parameter values are HTML-escaped before being passed to TT. Undeclared parameters are never passed to TT regardless of the URL.
Combining with api mode
Query strings work with api: true for dynamic JSON endpoints:
---
api: true
query_params:
- q
---
{"query": "[% query.q || '' %]", "results": []}
Query requests to API pages are not cached.
The control API is not callable from a browser page
This is a design position, not an omission, and it will not change.
The control API sends no CORS headers and answers a preflight with an explicit
405. Its authenticated surfaces serve agents, scripts and the manager - all of
which hold operator-issued credentials. A page running on an arbitrary origin
holds none, and a credential a browser could hold is a credential that is
exposed.
Understanding the reason matters more than the rule, because a variant design with the same problem will meet the same answer.
What to do instead, depending on what you actually need:
A browser needs to send something to the site. Use a form. It is same-origin, needs no sign-in, validates per field, stores the submission, and raises a notification - see Forms. This is the supported path, and it covers questionnaires, long pasted text and file uploads.
A browser application needs privileged work done. Serve the application as a static file from the site, and do the privileged work from somewhere that holds a credential - an agent over MCP, or a script calling this API. The page asks; the credentialled component acts.
A browser needs to read published content. It already can. Anything served on the public read path is same-origin and involves no CORS at all.
The two /.well-known/ discovery documents are deliberately open to any origin.
They carry no account data and exist so a browser-side onboarding probe can find
an instance. They are the only exception, and a test pins that.
Caching behaviour summary
| Mode | Query params | Cached |
|---|---|---|
| Normal | None or undeclared | Yes |
| Normal | Declared params present | No |
| raw: true | None | Yes |
| raw: true | Declared params present | No |
| api: true | None | Yes |
| api: true | Declared params present | No |
Content type caching
For raw: and api: pages with non-default content types, the
content type is cached alongside the .html file in
lazysite/cache/ct/. This ensures the correct content type is served
even when the page is served from cache.
The cache uses a flat file structure with : as a path delimiter:
lazysite/cache/ct/api:status.ct <- for /api/status
lazysite/cache/ct/docs:api.ct <- for /docs/api
Normal HTML pages do not write a .ct file. The cache directory is
protected from web access by the /lazysite/ URI block.
To clear content type cache entries:
find public_html/lazysite/cache/ct -name "*.ct" -delete
When a .html cache file is deleted, the corresponding .ct file
is also deleted automatically by the processor.