API overview
One gateway, four data endpoints, JSON over HTTPS. Protect text or a document, restore an answer, or plug into LiteLLM as a guardrail.
Base URL and authentication
Your deployment exposes one public origin, the Salus route or ingress. Data calls carry an API key in the Authorization header:
Authorization: Bearer salus_sk_AbCdEfGh.…
Keys are issued per tenant with a role and a list of allowed routes, and are shown once at creation. The tenant is taken from the key; a caller cannot select another tenant in headers or body. See Keys and tenants.
Endpoints
| Method | Path | Permission | Purpose |
|---|---|---|---|
| POST | /v1/detect/text |
detect_text |
Find and tokenise sensitive values in text |
| POST | /v1/detect/file |
detect_file |
The same for a PDF, DOCX, XLSX, PPTX, image or text file |
| POST | /v1/recover/text |
recover_text |
Put the original values back into an answer |
| POST | /beta/litellm_basic_guardrail_api |
litellm_guardrail |
Generic Guardrail endpoint for a LiteLLM gateway |
| GET | /health |
none | Liveness |
| GET | /ready |
none | Readiness: checks PostgreSQL and Redis |
Administration endpoints under /admin/v1/ manage tenants and keys and are described in Keys and tenants.
A first call
curl -fsS "$SALUS_BASE_URL/v1/detect/text" \
-H "Authorization: Bearer $SALUS_API_KEY" \
-H 'content-type: application/json' \
-d '{"text":"E-posta: [email protected]","context_id":"smoke-test"}'
{
"document_id": "6f1c…",
"context_id": "smoke-test",
"prompt_id": null,
"spans": [
{ "id": "…", "start": 9, "end": 31, "window_id": 0, "entity_type": "email",
"source": "regex", "confidence": 1.0, "verified": null,
"cluster_id": "…", "replacement": "EMAIL_dlwqzb" }
],
"redacted_text": "E-posta: EMAIL_dlwqzb",
"redacted_file": null,
"stats": { "windows_processed": 1, "sources": ["regex"], "warnings": [], "cache_hit": false, "elapsed_ms": 42 }
}
Request headers
Content-Typemust beapplication/jsonfor text calls andmultipart/form-datafor file calls. Anything else returns415.X-Request-Idis echoed on every response. Send your own (up to 128 characters) to correlate with your logs, or let the gateway generate one.- Responses carry
Cache-Control: no-store.
Errors
The gateway returns one envelope for every error:
{ "error": { "code": "UNPROCESSABLE_ENTITY", "message": "…", "request_id": "…" } }
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST |
Malformed body, unknown field, missing file part, mode not configured |
| 401 | UNAUTHORIZED |
Missing, malformed, unknown, expired or revoked key |
| 403 | ACCESS_DENIED |
Disabled key or tenant, route not allowed for the key, source IP not allowed |
| 404 | NOT_FOUND |
Unknown route, or an FPE document id that has expired |
| 413 | PAYLOAD_TOO_LARGE |
Body over the limit (16 MiB by default, 33 MiB with OCR) |
| 415 | UNSUPPORTED_MEDIA_TYPE |
Wrong content type, or a file format that is not supported |
| 422 | UNPROCESSABLE_ENTITY |
Document cannot be parsed, scanned input with OCR off, unreadable OCR, text over the model limit |
| 429 | RATE_LIMITED |
Tenant rate or concurrency limit; Retry-After is set |
| 503 | DEPENDENCY_UNAVAILABLE |
A detection layer, the verifier or Redis is unavailable. Nothing was sent. |
| 502 | UPSTREAM_UNAVAILABLE |
The gateway could not reach the engine |
Request bodies reject unknown fields, so a typo in a field name is a 400, never a silently ignored option.
Limits and timing
- Request body: 16 MiB by default. With the OCR service enabled the chart raises it to 33 MiB.
- Each downstream detection call has a 10 second budget with one retry on transport errors; the gateway waits up to 30 seconds for the engine (longer when OCR is enabled).
- Identical text submitted again in the same context returns from cache with
stats.cache_hit: true. Degraded results are never cached.
All example values in this documentation are synthetic.