API server
You will run piighost-api, the companion server of piighost, over a configuration published on the piighost catalog, then de-identify and restore a message over HTTP. Any process that speaks HTTP then shares one pipeline, loaded once. The conversation memory is kept on the server.
The configuration catalog:piighost/support-en finds names, addresses and organizations with the GLiNER2 model fastino/gliner2-multi-v1. It finds US identifiers and generic values such as emails with regexes. It also refuses to return a de-identified text that still holds a clear email address.
1. Install the server
The gliner2 extra pulls the model runtime the configuration needs.
uv add "piighost-api[gliner2]"pip install "piighost-api[gliner2]"2. Create an API key
piighost-api protects its routes with API keys. Each request must carry a valid key in the Authorization: Bearer <key> header. To manage these keys, the server uses keyshield, a Python library for API key management, installed with it. The server does not keep the keys in clear. It keeps their fingerprint, computed with Argon2 and a pepper, a secret added before the computation.
The server refuses to start without an API key, because it would otherwise open its routes to anyone. For a local trial, PIIGHOST_ALLOW_ANONYMOUS=true lets it start without a key, every route open. The keyshield command generates a key and a pepper.
keyshield generate
keyshield pepperEach command prints a line of this form, with your own values:
Set in your .env : "API_KEY_DEV=ak_v1-..."
Set in your .env : "SECRET_PEPPER=..."Export both in the shell that will start the server:
export API_KEY_DEV="ak_v1-..."
export SECRET_PEPPER="..."3. Start the server
piighost-api serve --config catalog:piighost/support-enThe server fetches the configuration from the catalog at every start, because the reference is not pinned to a commit. On the first start, it also downloads the GLiNER2 model. The log reports API keys loaded, auth enabled, then Pipeline ready: piighost/support-en:286909f6 (detector: composite), and uvicorn listens on http://127.0.0.1:8000.
Check it from another shell:
curl http://127.0.0.1:8000/healthThe output should be:
{"status":"ok","detector":"composite"}4. De-identify a message
Send a text and a thread_id to /v1/anonymize, with the key in the Authorization header.
curl -X POST http://127.0.0.1:8000/v1/anonymize \
-H "Authorization: Bearer $API_KEY_DEV" \
-H "Content-Type: application/json" \
-d '{"text": "Hi, I am Jane Doe, write to jane.doe@example.com.", "thread_id": "demo"}'The server answers 201 Created with a body of this shape:
{
"anonymized_text": "Hi, I am <<PERSON:1>>, write to <<EMAIL:1>>.",
"entities": [
{
"label": "PERSON",
"placeholder": "<<PERSON:1>>",
"detections": [
{"text": "Jane Doe", "label": "PERSON", "start_pos": 9, "end_pos": 17, "confidence": 0.9999873638153076}
]
},
{
"label": "EMAIL",
"placeholder": "<<EMAIL:1>>",
"detections": [
{"text": "jane.doe@example.com", "label": "EMAIL", "start_pos": 28, "end_pos": 48, "confidence": 1.0}
]
}
]
}Jane Doe becomes <<PERSON:1>> and jane.doe@example.com becomes <<EMAIL:1>>. The PERSON detection and its confidence come from the model, so your values can differ. The email comes from a regex, with a confidence of 1.0.
5. Restore the reply
Send a text carrying the placeholders to /v1/deanonymize, under the same thread_id. Here it is a reply a model could have written.
curl -X POST http://127.0.0.1:8000/v1/deanonymize \
-H "Authorization: Bearer $API_KEY_DEV" \
-H "Content-Type: application/json" \
-d '{"text": "Thanks <<PERSON:1>>, I will write to <<EMAIL:1>>.", "thread_id": "demo"}'The output should be:
{"text":"Thanks Jane Doe, I will write to jane.doe@example.com."}The server restores every placeholder the thread demo issued, whatever text carries it.
6. Call it from Python
PIIGhostClient drives the same routes from Python, with the key in its headers.
import asyncio
import os
from piighost.integrations.client import PIIGhostClient
async def main() -> None:
headers = {"Authorization": f"Bearer {os.environ['API_KEY_DEV']}"}
async with PIIGhostClient("http://127.0.0.1:8000", headers=headers) as client:
result = await client.anonymize("Hi, I am Jane Doe.", thread_id="demo")
print(result.text)
asyncio.run(main())The output should be:
Hi, I am <<PERSON:1>>.Jane Doe keeps <<PERSON:1>>, the token the thread demo gave it in step 4. The client and its integrations are covered in Remote client.
How it works
The server loads one thread pipeline at start and runs every route on it. A configuration that declares no [memory] section, as the two catalog configurations of this page, is served with the in-process memory. The threads then live in the server process, and vanish when it stops. To keep them across restarts, or to share them between several instances, declare a Redis memory, as Deployment shows.
See also
- To route an OpenAI or Anthropic client through the server, see OpenAI-compatible proxy and Anthropic-compatible proxy.
- To look up every route and its fields, see API endpoints.
- To look up every option and environment variable of the server, see Server CLI.
- To run the server in Docker with a shared memory, see Deployment.