---
icon: lucide/terminal
---
# Server CLI reference
Package: `piighost-api`
`piighost-api` is the command line of the companion server. `serve` starts the HTTP server. The `dataset` commands build and score a detection dataset from observation traces.
```text
piighost-api serve [--config SOURCE] [--host HOST] [--port PORT] [--log-level LEVEL]
piighost-api dataset extract --output FILE [--since DATE] [--until DATE] [--mode MODE] [--limit N]
piighost-api dataset metrics --input FILE [--output FILE] [--output-format FORMAT] [--match-mode MODE] [--iou-threshold FLOAT] [--source SOURCE]
```
The server requires Python 3.12 or later and `piighost>=2.0,<3`. Its extras add optional features:
| Extra | Adds |
|---|---|
| `gliner2` | `piighost[gliner2]`, for a configuration that runs a GLiNER2 detector |
| `observation` | the OpenTelemetry SDK and OTLP exporter, for exporting traces |
| `dataset` | the Langfuse SDK and `python-dotenv`, for `dataset extract` |
```bash
pip install "piighost-api[gliner2,observation]"
```
---
## `piighost-api serve`
Builds the pipeline once and serves the [API endpoints](api-endpoints.md) with uvicorn, in a single process.
```bash
piighost-api serve --config catalog:piighost/support-en --host 0.0.0.0 --port 8000
```
| Option | Default | Description |
|---|---|---|
| `--config`, `-c` | `PIIGHOST_CONFIG` | A TOML or JSON pipeline config file, or a catalog reference such as `catalog:piighost/support-en` |
| `--host` | `127.0.0.1` | Bind host |
| `--port` | `8000` | Bind port |
| `--log-level` | `info` | `debug`, `info`, `warning` or `error` |
- With neither `--config` nor `PIIGHOST_CONFIG`, the command prints `Missing --config or PIIGHOST_CONFIG.` with a usage hint and exits `1`. A file path that does not exist exits `1` with `Configuration file not found:`.
- A catalog reference loads the whole configuration the [piighost catalog](https://catalog.piighost.dev) publishes under that name. A reference pinned to a commit is fetched on the first start and read from the disk cache afterwards.
- A configuration that declares no `[memory]` section is served with the in-process memory, `in_memory`. Its threads live in the server process, so every instance holds its own. Several instances behind a load balancer need a shared `redis` or `sqlalchemy` memory, see [Multi-instance deployment](../multi-instance.md).
- Any top-level section can be overridden with a `PIIGHOST_` variable holding a JSON object, as for a file, see [Environment overrides](../configuration/toml.md). `PIIGHOST_MEMORY` thus adds a shared memory to a catalog configuration. The memory in the example below needs `piighost[crypto]` for its cipher.
- Without a key in an `API_KEY_` variable, the server refuses to start unless `PIIGHOST_ALLOW_ANONYMOUS` is set.
```bash
export PIIGHOST_MEMORY='{"type": "redis", "url": "redis://redis:6379/0", "hasher": {"type": "argon2"}, "cipher": {"type": "aesgcm"}}'
piighost-api serve --config catalog:piighost/support-en
```
---
## Environment variables
| Variable | Default | Effect |
|---|---|---|
| `PIIGHOST_CONFIG` | none | Config file or catalog reference, read when `--config` is absent |
| `API_KEY_` | none | One accepted API key per variable. The value is one printed by `keyshield generate` |
| `SECRET_PEPPER` | `keyshield`'s built-in pepper, with a warning | Pepper of the Argon2 hash the server keeps of each key, printed by `keyshield pepper` |
| `PIIGHOST_ALLOW_ANONYMOUS` | off | `1`, `true`, `yes` or `on` lets the server start with no key, every route then open. Also applies when the keys fail to load |
| `PIIGHOST_MAX_BODY_BYTES` | `1000000` | Largest request body accepted, beyond it `413` |
| `PIIGHOST_RATE_LIMIT` | off | `:` per client, `unit` one of `second`, `minute`, `hour`, `day`, such as `minute:300`. A malformed value stops the server at start |
| `PIIGHOST_OPENAI_UPSTREAM` | `https://api.openai.com/v1` | Upstream of `/openai/v1` when a request names none |
| `PIIGHOST_ANTHROPIC_UPSTREAM` | `https://api.anthropic.com/v1` | Upstream of `/anthropic/v1` when a request names none |
| `PIIGHOST_ANTHROPIC_ANONYMIZE_SYSTEM` | `false` | `1`, `true`, `yes` or `on` de-identifies the system prompt too |
| `PIIGHOST_ANTHROPIC_PLACEHOLDER_NOTE` | empty, no note | `default` adds the built-in note on placeholders. Any other text is used as the note itself |
| `PIIGHOST_ANTHROPIC_NOTE_PLACEMENT` | `system` | `user` puts the note in the first user message, any other value in the system prompt |
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_ENDPOINT` | none | An OTLP endpoint turns on trace export. When both are set, the first one listed wins. Needs the `observation` extra |
| `OTEL_SERVICE_NAME` | `piighost-api` | Service name of the exported traces |
| `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` | none | Credentials of `dataset extract` |
The pipeline reads its own secrets (`PIIGHOST_HASH_PEPPER`, `PIIGHOST_CIPHER_KEY`, `PIIGHOST_DATABASE_URL` and `MISTRAL_API_KEY`). `PIIGHOST_CATALOG_URL` names a private catalog. These variables are listed in the [TOML reference](../configuration/toml.md). The other `OTEL_*` variables, headers included, are read by the OpenTelemetry exporter itself, see [Observation](../observation.md).
The Docker image reads four more, listed in [Deploy a production pipeline](../deployment.md).
---
## `piighost-api dataset extract`
Reads traces from Langfuse and writes one JSONL record per trace. It needs the `dataset` extra and `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY`, read from the environment or from a `.env` file in the working directory. Without them, it exits `1`.
```bash
piighost-api dataset extract --output dataset.jsonl --since 2026-09-01 --limit 1000
```
| Option | Default | Description |
|---|---|---|
| `--output`, `-o` | required | JSONL file to write |
| `--since` | none | Skip traces older than this date, `%Y-%m-%d`, `%Y-%m-%dT%H:%M:%S` or `%Y-%m-%d %H:%M:%S` |
| `--until` | none | Skip traces newer than this date, same formats |
| `--mode` | `all` | `hitl`, `model-only` or `all` |
| `--limit` | none | Stop after this many records |
| `--mode` | Trace name read | `entities` taken from |
|---|---|---|
| `hitl` | `piighost.hitl_correction` | the trace output's `detections`, the human correction |
| `model-only` | `piighost.anonymize` | the output `detections` of its `piighost.detect` child |
| `all` | both | per trace |
!!! warning
No route of the server emits `piighost.hitl_correction`, so `hitl` finds no trace. A corrected message is traced as `piighost.anonymize`, like any other, and `model-only` reads it as a model trace.
A trace without input text, or a model trace without its `piighost.detect` child, is skipped. The command ends with `Wrote N records to FILE (M skipped).`
```json
{
"text": "Hi Jane Doe, from Acme in Boston",
"entities": [[3, 11, "PERSON"], [18, 22, "ORGANIZATION"], [26, 32, "LOCATION"]],
"model_entities": [[3, 11, "PERSON"], [18, 22, "LOCATION"]],
"labels_universe": [],
"source": "hitl",
"trace_id": "...",
"session_id": "...",
"created_at": "..."
}
```
| Field | Content |
|---|---|
| `entities` | The reference spans as `[start, end, label]`. They are the human correction for a `hitl` record, and the model output for a `model` record |
| `model_entities` | The model's spans, equal to `entities` on a `model` record |
| `labels_universe` | The `labels` of a correction trace's input, empty on a `model` record |
| `source` | `hitl` or `model` |
---
## `piighost-api dataset metrics`
Scores the model against the reference of a JSONL file written by `dataset extract`, label by label. It needs no extra.
```bash
piighost-api dataset metrics --input dataset.jsonl
```
| Option | Default | Description |
|---|---|---|
| `--input`, `-i` | required | JSONL file to read |
| `--output`, `-o` | stdout | File to write the report to |
| `--output-format` | `table` | `table`, `csv` or `json` |
| `--match-mode` | `strict` | `strict` matches span and label exactly, `lenient` matches a same-label span whose overlap ratio reaches `--iou-threshold` |
| `--iou-threshold` | `0.5` | Overlap floor in `lenient` mode |
| `--source` | `all` | `hitl`, `model` or `all`, the records to score |
On the record above, the table reads:
```text
label tp fp fn P R F1
--------------------------------------------------------------
LOCATION 0 1 1 0.00 0.00 0.00
ORGANIZATION 0 0 1 0.00 0.00 0.00
PERSON 1 0 0 1.00 1.00 1.00
--------------------------------------------------------------
macro avg - - - 0.33 0.33 0.33
micro avg - - - 0.50 0.33 0.40
Label confusion (model -> human, same span):
LOCATION -> ORGANIZATION: 1
```
`tp` counts a model span the reference holds, `fp` a model span it lacks, `fn` a reference span the model missed. `P`, `R` and `F1` are the precision, the recall and their harmonic mean. The confusion section lists the spans where model and reference agree on the offsets and differ on the label.
---
## See also
- [API endpoints](api-endpoints.md): every route the server serves.
- [Deploy a de-identification API](../getting-started/api-server.md): a first server, step by step.
- [CLI](cli.md): the `piighost` command of the library.