Contributing
Updated 2026-10-29.
Docs pages are markdown files in citadel-go/docs/developer/, served by Citadel at GET /v1/docs.
Workflow
- Edit
citadel-go/docs/developer/<section>/<page>.md. - Run the tests and the example checker.
- Commit the page change in the same commit as the code change.
cd citadel-go
go test ./internal/api/devdocs/
docs/developer/_tools/check-examples.py llm/streamingA change to a public endpoint, model, limit or known issue requires a page change in the same commit.
Files
citadel-go/docs/developer/
sections.yml sidebar sections, in order
<section>/<page>.md one page, served at /docs/<section>/<page>
_coverage/undocumented.yml routes without a docs page, by reason
_tools/check-examples.py example checkerCitadel does not serve directories whose names start with _.
Front matter
---
title: Streaming
section: llm
order: 3
status: beta
updated: 2026-10-29
summary: Server-sent events for chat completions.
routes:
- "POST /v1/chat/completions"
---| Field | Value |
|---|---|
title | Page title; the body starts at ## |
section | Directory name |
order | Position in the section, unique |
status | stable, beta, experimental or coming |
updated | YYYY-MM-DD |
summary | One line, under 80 characters |
routes | Routes on the page, as registered: "METHOD /v1/path/{param}" |
Register
- Name the endpoint, parameter, field, header, status code, model id or limit. Start no sentence with a pronoun.
- Use literal verbs: returns, accepts, requires, defaults to, is limited to, fails with, resets at.
- State facts and instructions. Leave out motivation, narrative and commentary.
- Write instructions in the imperative: "Set
max_tokens." - Put data in tables. Table cells contain values. A sentence goes below the table.
- Leave out facts that do not change a request or a response.
Page structure
A product page (<section>/overview.md) has these ## sections, in this order: Quick start, Reference, Models,
Limits, Known issues, Examples, Changelog. Quick start is required. Leave out a section with no content.
- Known issues: each list item starts with the date first seen,
YYYY-MM-DD. Onllm/known-issues.md, each###issue starts withSince YYYY-MM-DD. Status: .... - Changelog: one dated line per change, newest first.
- A
comingpage has one##section, Planned, and no routes. - Other pages use only the sections their content requires.
Markdown
Code tabs. Consecutive fenced blocks with tab=<label> render as one block with tabs. Labels: curl, Python,
JavaScript.
```bash tab=curl
curl https://citadel.albastov.com/v1/models -H "Authorization: Bearer $CITADEL_API_KEY"
```
```python tab=Python
print("the same example in Python")
```Block attributes. title="..." labels a block. check=skip excludes a block from the example checker.
check=error requires the block to fail with an HTTP error.
Examples. The example checker runs every bash, python and js block as written. Read the key from
CITADEL_API_KEY. Use qwen3.8-27b. Copy responses from real calls.
Links. Link pages with the full URL: https://albastov.com/docs/<section>/<page>#<heading>. A heading anchor is the
heading text in lowercase, without punctuation, with - for spaces.
Notes. A note is a paragraph starting with **Note.**.
Public content only. No internal host names, IP addresses, container names or tokens. No admin, internal or owner-only routes.
Tests
go test ./internal/api/devdocs/ runs the following tests. Each failure message names the file and the fix.
| Test | Fails on |
|---|---|
TestEveryRouteIsDocumentedOrExempt | Undocumented, stale or duplicate route |
TestProseRegister | Pronoun-led sentence; banned phrase |
TestProductPagesFollowTheSkeleton | Product page sections out of order |
TestKnownIssuesAreDated | Known issue without a date |
TestDocsLinksResolve | Link to a missing page or heading |
TestNothingInternalInPublicDocs | Internal host, IP address, key or route |
TestLLMGuideIsTheLLMPage | GET /v1/llm/docs differs from llm/overview.md |
Banned phrases: lives, live in, keep true, keeps the docs, protect, can hit, what you get,
under the hood, simply, just. The prose test skips code blocks and inline code.
undocumented.yml categories: admin, owner-only, site-session, internal, hook, dev, not-yet.
Example checker
export CITADEL_API_KEY=hlk_...
citadel-go/docs/developer/_tools/check-examples.py # every page
citadel-go/docs/developer/_tools/check-examples.py llm/streaming| Option | Value |
|---|---|
| Pages | section/page arguments; default all |
--base | API base URL; default https://citadel.albastov.com/v1 |
--list | List blocks without running them |
| Requirements | Python 3 with openai; Node 18 or later |
Run the checker after a change to an example, a model, a limit or an engine.
Docs API
GET /v1/docs and GET /v1/docs/{section}/{page} require no key. Responses include
Cache-Control: public, max-age=60 and an ETag. A request with a matching If-None-Match returns 304.
{
"base_url": "https://citadel.albastov.com/v1",
"updated": "2026-10-29",
"sections": [
{
"id": "llm",
"title": "LLM",
"status": "beta",
"pages": [
{
"section": "llm", "slug": "streaming", "path": "llm/streaming",
"title": "Streaming", "order": 3, "status": "beta", "updated": "2026-10-29",
"summary": "Server-sent events for chat completions.",
"routes": ["POST /v1/chat/completions"],
"headings": [{"level": 2, "text": "Example", "id": "example"}],
"text": "..."
}
]
}
]
}| Field | Value |
|---|---|
sections[].status | Status of the section's first page |
pages[].headings | ## and ### headings with anchor ids |
pages[].text | Page as plain text |
GET /v1/docs/{section}/{page} returns the page's front matter fields, headings and markdown (the body without
front matter). An unknown page returns 404 not_found.
curl -s https://citadel.albastov.com/v1/docs/llm/streaming | python3 -c '
import json, sys
page = json.load(sys.stdin)
print(page["title"], page["status"], page["updated"])
print([h["text"] for h in page["headings"]])'