Skip to content

Contributing

Updated 2026-10-29.

Docs pages are markdown files in citadel-go/docs/developer/, served by Citadel at GET /v1/docs.

Workflow

  1. Edit citadel-go/docs/developer/<section>/<page>.md.
  2. Run the tests and the example checker.
  3. 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/streaming

A 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 checker

Citadel 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"
---
FieldValue
titlePage title; the body starts at ##
sectionDirectory name
orderPosition in the section, unique
statusstable, beta, experimental or coming
updatedYYYY-MM-DD
summaryOne line, under 80 characters
routesRoutes 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. On llm/known-issues.md, each ### issue starts with Since YYYY-MM-DD. Status: ....
  • Changelog: one dated line per change, newest first.
  • A coming page 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.

TestFails on
TestEveryRouteIsDocumentedOrExemptUndocumented, stale or duplicate route
TestProseRegisterPronoun-led sentence; banned phrase
TestProductPagesFollowTheSkeletonProduct page sections out of order
TestKnownIssuesAreDatedKnown issue without a date
TestDocsLinksResolveLink to a missing page or heading
TestNothingInternalInPublicDocsInternal host, IP address, key or route
TestLLMGuideIsTheLLMPageGET /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
OptionValue
Pagessection/page arguments; default all
--baseAPI base URL; default https://citadel.albastov.com/v1
--listList blocks without running them
RequirementsPython 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.

GET /v1/docs (abridged)
{
  "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": "..."
        }
      ]
    }
  ]
}
FieldValue
sections[].statusStatus of the section's first page
pages[].headings## and ### headings with anchor ids
pages[].textPage 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"]])'