Quick Start
Get OGuardAI running and make your first API call in under a minute
Availability: The registry, image and package installs below are private today and provided under a written agreement with Oronts. Request access at /request.
Start the Server
docker run -p 3000:3000 \
-e GUARDAI_SESSION_SECRET=$(openssl rand -base64 32) \
-e GUARDAI_HOST=0.0.0.0 \
-e GUARDAI_ALLOW_DEV_NON_LOOPBACK=1 \
ghcr.io/oronts/oguardai/server:latestOne binary. Built-in regex detectors. Sealed sessions. No Python, no Redis.
The baked config binds loopback under dev auth, so a container needs GUARDAI_HOST=0.0.0.0 to
accept the published port and GUARDAI_ALLOW_DEV_NON_LOOPBACK=1 to accept that dev auth is exposed.
For a real deployment, use a production profile or the Helm chart instead of dev auth.
How It Works
Check Health
curl http://localhost:3000/v1/health
# {"status":"healthy","version":"0.1.0","uptime_seconds":1.2, ...}
# The full response also carries a "components" block (detector, session, policies) with a per-component status and message,
# and a "license" block reporting the resolved licence. With no licence configured it reads "absent" and the server runs
# the developer edition; nothing is gated by it. See /docs/guides/licensing.Transform Text
Send sensitive text to OGuardAI. It detects PII, replaces it with semantic tokens, and returns the safe version along with an encrypted session state blob.
curl -X POST http://localhost:3000/v1/transform \
-H "Content-Type: application/json" \
-d '{"input": "Contact erika.mustermann@example.com about invoice #12345"}'Response:
{
"safe_text": "Contact {{email:e_001:7b1068662ab5aa11bb22cc33dd44ee55}} about invoice #12345",
"session_id": "01916a3e-7b2c-7000-8000-000000000001",
"session_state": "eyJhbGci...",
"entities": [
{
"token": "{{email:e_001:7b1068662ab5aa11bb22cc33dd44ee55}}",
"type": "email",
"span": { "start": 8, "end": 23 }
}
]
}The third token segment is a random per-token capability suffix. Rehydrate resolves a token only
on an exact {{type:id:cap}} match, so a guessed or truncated token is left unresolved (fail
closed). Pass the LLM output back verbatim.
Note:
span.startandspan.endare UTF-8 byte offsets into the input, not character or code-unit offsets. They match Rust string slicing. If you slice by them in Python, JavaScript, or Java, which index by code point or UTF-16 code unit, decode the input to bytes first (for exampletext.encode("utf-8")[start:end].decode("utf-8")in Python); otherwise any non-ASCII input is sliced at the wrong position.
Rehydrate (Restore)
After the LLM responds using the tokenized text, restore the original values:
curl -X POST http://localhost:3000/v1/rehydrate \
-H "Content-Type: application/json" \
-d '{
"output": "I have emailed {{email:e_001:7b1068662ab5aa11bb22cc33dd44ee55}} about the invoice.",
"session_state": "eyJhbGci..."
}'Response:
{
"restored_text": "I have emailed erika.mustermann@example.com about the invoice."
}What Happened
- OGuardAI detected
erika.mustermann@example.comas an email entity - Replaced it with
{{email:e_001:7b1068662ab5aa11bb22cc33dd44ee55}}, a semantic token the LLM can reason about - Stored the mapping in an AES-256-GCM encrypted session blob
- On rehydrate, decrypted the blob and restored the original value
The LLM never saw the real email address.
Detect Only
You can also run detection without transformation to see what entities OGuardAI finds:
curl -X POST http://localhost:3000/v1/detect \
-H "Content-Type: application/json" \
-d '{"input": "SSN: 123-45-6789, email: erika.mustermann@example.com", "include_values": false}'Round-Trip Test
Run a full transform-then-rehydrate cycle to verify end-to-end behavior:
# Transform
RESPONSE=$(curl -s -X POST http://localhost:3000/v1/transform \
-H "Content-Type: application/json" \
-d '{"input": "Contact Erika at erika.mustermann@example.com or call 555-0123."}')
echo "$RESPONSE" | jq .
# Rehydrate
SESSION_STATE=$(echo "$RESPONSE" | jq -r '.session_state')
curl -s -X POST http://localhost:3000/v1/rehydrate \
-H "Content-Type: application/json" \
-d "{\"output\": $(echo "$RESPONSE" | jq '.safe_text'), \"session_state\": \"$SESSION_STATE\"}" \
| jq .Using the CLI
If you have the OGuardAI CLI installed, you can transform and detect from the command line:
oguardai transform --input "Contact erika.mustermann@example.com"
oguardai detect --input "SSN: 123-45-6789"The sealed session state is withheld from stdout by default so the token to value map never lands in
logs, terminal scrollback, or CI output. Save it to an owner-only file with --session-state-out and
read it back on rehydrate with --session-state-file, or print it explicitly with --emit-session-state:
oguardai transform --input "Contact erika.mustermann@example.com" --session-state-out sess.blob
# transform prints a "Safe text:" line with capped tokens like {{email:e_001:<cap>}};
# pass that exact safe text back to rehydrate:
oguardai rehydrate --input "Contact {{email:e_001:<cap>}}" --session-state-file sess.blobUsing the OpenAI Proxy
For the fastest integration with existing OpenAI code, start the proxy and change one URL:
# Start the proxy from the published image (local eval: GUARDAI_PROXY_DEV=1 runs an unauthenticated
# dev proxy, and --network host serves it on the host's localhost:8081)
docker run --network host -e GUARDAI_PROXY_DEV=1 ghcr.io/oronts/oguardai/proxy:0.1.0 \
--target https://api.openai.com --policy default --port 8081from openai import OpenAI
client = OpenAI(
api_key="sk-...",
base_url="http://localhost:8081/v1" # Only change needed
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Contact erika.mustermann@example.com or call +49 30 12345678"}]
)
# OpenAI sees: "Contact `{{email:e_001:4c19f2d8b7a3aa11bb22cc33dd44ee55}}` or call `{{phone:ph_001:d82a05c1f4e6aa11bb22cc33dd44ee55}}`"
# Response is automatically restored before reaching your codeNote:
GUARDAI_PROXY_DEV=1runs a local, unauthenticated dev proxy, which is why the example above sends no proxy key. A real deployment setsGUARDAI_PROXY_API_KEYinstead, and every client then passes it asdefault_headers={"X-GuardAI-API-Key": "..."}. See the OpenAI Proxy guide for the authenticated setup, key rotation, and the dedicated metrics key.
Docker Compose (Full Stack)
For a full local setup with NER detection:
GUARDAI_SESSION_SECRET=$(openssl rand -base64 32) \
GUARDAI_API_KEY_1=$(openssl rand -hex 32) \
GUARDAI_DETECTOR_API_KEY=$(openssl rand -hex 32) \
GUARDAI_REDIS_PASSWORD=$(openssl rand -hex 32) \
docker compose -f deploy/docker/docker-compose.yml upThis starts the Rust API server (port 3000), Python NER detector (port 9090), and Redis. The full
stack requires all four secrets above: the server refuses to start without them. GUARDAI_DETECTOR_API_KEY
authenticates the server/detector hop, and GUARDAI_REDIS_PASSWORD authenticates the Redis connection
(keep it URL-safe, which openssl rand -hex is).
Common Endpoints
| Endpoint | Purpose |
|---|---|
POST /v1/transform | Transform text, return safe output + session state |
POST /v1/rehydrate | Restore tokens using session state |
POST /v1/detect | Detect entities only (no transformation) |
POST /v1/evaluate-policy | Evaluate policy against entities |
GET /v1/health | Health check |
GET /v1/capabilities | List entity types, languages, detectors |
POST /v1/transform/stream | Stream transform with SSE |
POST /v1/rehydrate/stream | Stream rehydrate with SSE |
These are the endpoints most integrations start with. The runtime exposes more (batch, RAG, file and image transforms, sessions, revocation, admin); see the API Reference for the full route set, scopes, and error codes.
Next Steps
- Installation: explore all deployment options
- Configuration: configuration file and environment variable reference
- SDK Guide: integrate with Python or TypeScript
- OpenAI Proxy: transparent proxy for OpenAI/Anthropic
- Security Guarantees: what OGuardAI does and does not guarantee