# citum-server
`citum-server` exposes Citum citation rendering through JSON-RPC. Use it when a
client should talk to a long-running process instead of linking
`citum-engine` directly.
The same dispatcher is available over two transports:
- stdin/stdout: newline-delimited JSON-RPC, enabled in every build
- HTTP: `POST /rpc`, enabled by the default `http` feature
The document-level RPC method is `format_document`. The single-citation and
bibliography methods are convenience methods for smaller workflows. The session
API (`open_session` and friends) supports stateful incremental editing and is
enabled by the default `session` feature.
## Install
```sh
cargo install citum-server --locked
```
## Run It
Start stdio mode (reads one JSON object per line from stdin, writes one per
line to stdout):
```sh
citum-server
```
For HTTP mode:
```sh
citum-server --http --port 9000
```
From the workspace root (development / contributing):
```sh
cargo run -p citum-server -- --http --port 9000
```
The examples below assume they are run from the Citum repository root so the
relative style path `styles/embedded/apa-7th.yaml` resolves. Outside the
repository, replace that value with an absolute path to a Citum YAML style.
## JSON-RPC Envelope
Every request has this shape:
```json
{
"id": 1,
"method": "render_citation",
"params": {}
}
```
Successful responses echo the request ID and include `result`:
```json
{
"id": 1,
"result": "..."
}
```
Errors echo the request ID when available and include `error`:
```json
{
"id": 1,
"error": "missing required field: style_path"
}
```
`refs` in `render_citation` and `render_bibliography` is an inline JSON map of
reference objects. `refs` in `format_document` accepts a tagged input object —
see the `refs` input section below. Reference data uses native Citum schema.
Dates are EDTF strings such as `"1988"`, not CSL-JSON `date-parts` objects.
## Method Summary
| Method | Required params | Optional params | Result |
|---|---|---|---|
| `render_citation` | `style_path`, `refs`, `citation` | `output_format`, `inject_ast_indices` | rendered citation string |
| `render_bibliography` | `style_path`, `refs` | `output_format`, `inject_ast_indices` | `{format, content, entries}` |
| `validate_style` | `style_path` | none | `{valid, warnings}` |
| `format_document` | `style`, `refs`, `citations` | `output_format`, `locale`, `document_options` | `{formatted_citations, bibliography, warnings}` |
| `open_session` ¹ | `style` | `style_overrides`, `locale`, `output_format`, `document_options` | `{session_id}` |
| `put_references` ¹ | `refs` | `session_id` | `{}` |
| `insert_citations_batch` ¹ | `citations` | `session_id` | session mutation result |
| `insert_citation` ¹ | `citation` | `session_id`, `position` | session mutation result |
| `update_citation` ¹ | `citation_id`, `citation` | `session_id`, `position` | session mutation result |
| `delete_citation` ¹ | `citation_id` | `session_id` | session mutation result |
| `preview_citation` ¹ | `items` | `session_id`, `position` | preview result |
| `get_citations` ¹ | — | `session_id` | `{formatted_citations}` |
| `get_bibliography` ¹ | — | `session_id` | `{bibliography}` |
| `close_session` ¹ | — | `session_id` | `{}` |
¹ Requires the `session` feature (default-on). `session_id` is optional in
stdio (omit it or pass `"default"`) and required in HTTP. See [Session API](#session-api).
Supported `output_format` values are `plain` (default), `html`, `djot`,
`latex`, and `typst`.
`render_citation`, `render_bibliography`, and `validate_style` use
`style_path`, a string path to a local Citum YAML style. `format_document` uses
the richer `style` object:
```json
{ "kind": "path", "value": "styles/embedded/apa-7th.yaml" }
```
Other `style.kind` values are `id`, `uri`, and `yaml`.
## Stdio Example: `render_citation`
The stdio transport expects each request on a single line:
```sh
printf '%s\n' '{"id":1,"method":"render_citation","params":{"style_path":"styles/embedded/apa-7th.yaml","refs":{"hawking1988":{"id":"hawking1988","class":"monograph","type":"book","title":"A Brief History of Time","author":[{"family":"Hawking","given":"Stephen"}],"issued":"1988"}},"citation":{"id":"cite-1","items":[{"id":"hawking1988"}]}}}' \
| citum-server
```
The response is a JSON object whose `result` is the rendered citation string.
## HTTP Example: `render_bibliography`
Start the server in another terminal:
```sh
citum-server --http --port 9000
```
Then send a request:
```sh
curl -s http://localhost:9000/rpc \
-H 'Content-Type: application/json' \
-d '{
"id": 2,
"method": "render_bibliography",
"params": {
"style_path": "styles/embedded/apa-7th.yaml",
"output_format": "html",
"refs": {
"hawking1988": {
"id": "hawking1988",
"class": "monograph",
"type": "book",
"title": "A Brief History of Time",
"author": [{ "family": "Hawking", "given": "Stephen" }],
"issued": "1988"
}
}
}
}'
```
The response `result` contains:
- `format`: the selected output format
- `content`: the rendered bibliography
- `entries`: optional plain-text entry strings when the renderer supplies them
## Full Document Example: `format_document`
Use `format_document` when the client has the whole document citation order.
This is the preferred editor and word-processor integration method because it
lets the engine render document-sensitive citation state and bibliography output
in one call.
### HTTP `format_document`
Use this form when the server is running with `--http`:
```sh
curl -s http://localhost:9000/rpc \
-H 'Content-Type: application/json' \
-d '{
"id": 3,
"method": "format_document",
"params": {
"style": { "kind": "path", "value": "styles/embedded/apa-7th.yaml" },
"output_format": "html",
"refs": {
"smith2010": {
"id": "smith2010",
"class": "monograph",
"type": "book",
"title": "Nationalism: Theory, Ideology, History",
"author": [{ "family": "Smith", "given": "Anthony D." }],
"issued": "2010",
"publisher": { "name": "Polity" }
}
},
"citations": [
{
"id": "cite-1",
"items": [
{
"id": "smith2010",
"locator": { "label": "page", "value": "10" }
}
]
}
],
"document_options": {
"show_semantics": true
}
}
}'
```
### Stdio JSON-RPC `format_document`
Use this form when the server is running in its default stdin/stdout mode. The
request must be one JSON object on one line:
```sh
printf '%s\n' '{"id":3,"method":"format_document","params":{"style":{"kind":"path","value":"styles/embedded/apa-7th.yaml"},"output_format":"html","refs":{"smith2010":{"id":"smith2010","class":"monograph","type":"book","title":"Nationalism: Theory, Ideology, History","author":[{"family":"Smith","given":"Anthony D."}],"issued":"2010","publisher":{"name":"Polity"}}},"citations":[{"id":"cite-1","items":[{"id":"smith2010","locator":{"label":"page","value":"10"}}]}],"document_options":{"show_semantics":true}}}' \
| citum-server
```
The response `result` has the same top-level shape over HTTP and stdio:
```json
{
"id": 3,
"result": {
"formatted_citations": [
{
"id": "cite-1",
"text": "(<span class=\"citum-citation\" data-ref=\"smith2010\">Smith, <span class=\"citum-issued\">2010</span>, <span class=\"citum-variable\">p. 10</span></span>)",
"ref_ids": ["smith2010"]
}
],
"bibliography": {
"format": "html",
"content": "<div class=\"citum-bibliography\">...</div>",
"entries": [
{
"id": "smith2010",
"text": "<div class=\"citum-bibliography\">...</div>",
"metadata": {
"author": "Smith",
"year": "2010",
"title": "Nationalism: Theory, Ideology, History"
}
}
]
},
"warnings": []
}
}
```
Clients should read bibliography output from `result.bibliography`, not from
`result.formatted_citations`.
## `refs` Input for `format_document`
The `refs` field in `format_document` accepts a tagged input object, mirroring
the `style` field. These are alternative shapes, not one combined JSON value:
```json
{ "kind": "path", "value": "/abs/path/to/refs.yaml" }
```
```json
{ "kind": "yaml", "value": "hawking1988:\n id: hawking1988\n ..." }
```
```json
{ "kind": "json", "value": { "hawking1988": { ... } } }
```
A bare JSON object (no `kind` key) is also accepted as `{"kind": "json"}` for
backward compatibility.
`path` and `yaml` have the server read and parse the bibliography file; `json`
passes inline reference data directly. Use `path` when the server and client
share a filesystem (e.g., when called via pipe from a LuaLaTeX document).
## Loading Local Files
Style and bibliography files on the same filesystem can be referenced by path
in both `style` and `refs`:
```sh
printf '%s\n' '{
"id": 4,
"method": "format_document",
"params": {
"style": { "kind": "path", "value": "styles/embedded/apa-7th.yaml" },
"refs": { "kind": "path", "value": "examples/document-refs-native.json" },
"output_format": "html",
"citations": [{ "id": "cite-1", "items": [{ "id": "kuhn1962" }] }]
}
}' | citum-server
```
For HTTP clients that prefer inline data, assemble the request with `jq`:
```sh
jq -n \
--arg style_path "styles/embedded/apa-7th.yaml" \
--slurpfile refs examples/document-refs-native.json \
--slurpfile citations examples/document-citations.json \
'{
id: 4,
method: "format_document",
params: {
style: { kind: "path", value: $style_path },
refs: { kind: "json", value: $refs[0] },
citations: $citations[0],
output_format: "html"
}
}' | curl -s http://localhost:9000/rpc \
-H 'Content-Type: application/json' \
-d @-
```
## Session API
The session API is for editor and word-processor integrations where the
reference list and citation order evolve as the user edits. Instead of
re-sending the entire document on every change, a session holds state between
calls so you can insert, update, or delete individual citations incrementally.
### Session model
**Stdio transport:** one implicit session per process. `open_session` returns
`session_id: "default"`. Pass `session_id` or omit it — both work.
**HTTP transport:** multiple concurrent named sessions, each identified by the
`session_id` returned from `open_session`. Sessions expire after 30 minutes of
inactivity. Always pass the `session_id` you received.
### Session mutation result
`insert_citation`, `update_citation`, `delete_citation`, and
`insert_citations_batch` all return the same shape:
```json
{
"formatted_citations": [{ "id": "cite-1", "text": "...", "ref_ids": ["..."] }],
"bibliography": { "format": "html", "content": "...", "entries": [...] },
"warnings": []
}
```
### Lifecycle
```
open_session → put_references → insert_citation(s)
↕ update_citation / delete_citation
get_citations / get_bibliography
close_session
```
### `style_overrides`
`open_session` accepts an optional `style_overrides` field: a partial YAML or
JSON fragment merged over the resolved base style using the same null-aware
typed-merge semantics as `extends` inheritance. The base style is never
mutated, so overrides are scoped to the session.
### Stdio session example
```sh
# open a session
printf '%s\n' '{"id":1,"method":"open_session","params":{"style":{"kind":"path","value":"styles/embedded/apa-7th.yaml"},"output_format":"html"}}' \
| citum-server
# put references (session_id optional in stdio — defaults to "default")
printf '%s\n' '{"id":2,"method":"put_references","params":{"refs":{"hawking1988":{"id":"hawking1988","class":"monograph","type":"book","title":"A Brief History of Time","author":[{"family":"Hawking","given":"Stephen"}],"issued":"1988"}}}}' \
| citum-server
# insert a citation
printf '%s\n' '{"id":3,"method":"insert_citation","params":{"citation":{"id":"cite-1","items":[{"id":"hawking1988"}]}}}' \
| citum-server
```
### HTTP session example
```sh
# open a session
SESSION=$(curl -s http://localhost:9000/rpc \
-H 'Content-Type: application/json' \
-d '{"id":1,"method":"open_session","params":{"style":{"kind":"path","value":"styles/embedded/apa-7th.yaml"},"output_format":"html"}}' \
| jq -r '.result.session_id')
# put references
curl -s http://localhost:9000/rpc \
-H 'Content-Type: application/json' \
-d "{\"id\":2,\"method\":\"put_references\",\"params\":{\"session_id\":\"$SESSION\",\"refs\":{\"hawking1988\":{\"id\":\"hawking1988\",\"class\":\"monograph\",\"type\":\"book\",\"title\":\"A Brief History of Time\",\"author\":[{\"family\":\"Hawking\",\"given\":\"Stephen\"}],\"issued\":\"1988\"}}}}"
# insert a citation and receive updated formatted output
curl -s http://localhost:9000/rpc \
-H 'Content-Type: application/json' \
-d "{\"id\":3,\"method\":\"insert_citation\",\"params\":{\"session_id\":\"$SESSION\",\"citation\":{\"id\":\"cite-1\",\"items\":[{\"id\":\"hawking1988\"}]}}}"
# close the session
curl -s http://localhost:9000/rpc \
-H 'Content-Type: application/json' \
-d "{\"id\":4,\"method\":\"close_session\",\"params\":{\"session_id\":\"$SESSION\"}}"
```
## Discovery
HTTP mode exposes read-only discovery endpoints:
```sh
curl http://localhost:9000/rpc/methods
```
`GET /rpc/methods` returns supported methods, required fields, and optional
fields.
```sh
curl http://localhost:9000/rpc/schema
```
`GET /rpc/schema` returns JSON Schemas for method parameters when the binary is
built with the `schema` feature. Sending `GET /rpc` returns `405 Method Not
Allowed` with a JSON hint explaining that `/rpc` expects `POST`.
## Features
| Feature | Default | Description |
|---|---|---|
| `session` | on | Enables the stateful session API (`open_session`, `insert_citation`, etc.) |
| `async` | on through `http` | Enables the Tokio runtime dependency used by HTTP transport |
| `http` | on | Enables the axum HTTP server and implies `async` |
| `schema` | off | Enables `/rpc/schema` and implies `http` plus schema type support |
| `schema-types` | off | Enables schema derivations without the HTTP schema endpoint |
Build a stdio-only binary with:
```sh
cargo build -p citum-server --no-default-features
cargo install citum-server --no-default-features
```
## Performance
A simulation script benchmarks a server-backed word-processor workflow:
```sh
cargo build --release -p citum-server
node scripts/benchmark-rpc-workflow.js
```
## Dependencies
`citum-server` depends on `citum-engine`, `citum-schema`, and `citum_store` for
rendering and the standard resolver chain. It does not depend on
`citum-migrate`.