API

Detect PII in one call.

Send text, get back detected entities with spans and a single authoritative block/allow decision. Hosted in Canada.

Base URL

https://vervian-api.fly.dev

Endpoint

POST /pii/v1/detect

Auth

X-API-Key header

Contract

GET /pii/v1/openapi.json

API keys are provisioned per organization during pilot onboarding. Request access. The machine-readable contract is served live at /pii/v1/openapi.json (OpenAPI 3.1).

Quickstart

Your first detection.

# curl
curl -X POST https://vervian-api.fly.dev/pii/v1/detect \
  -H "X-API-Key: $PII_SHIELD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "John Smith, SSN 123-45-6789"}'
# JavaScript (fetch)
const res = await fetch("https://vervian-api.fly.dev/pii/v1/detect", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.PII_SHIELD_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ text: "John Smith, SSN 123-45-6789" }),
});
const result = await res.json();
if (result.should_block) {
  // Do not forward this text to your LLM as-is.
}
# Response (captured live, abridged)
{
  "request_id": "64eb5b69-f0f5-49c6-8e38-aa9b4a1fe439",
  "should_block": true,
  "severity": "high",
  "total_score": 12.5,
  "threshold": 3,
  "entities": [
    { "entity_id": "E002", "type": "FIRST_NAME", "text": "John", "span": [0, 4], "severity": "high" },
    { "entity_id": "E003", "type": "LAST_NAME", "text": "Smith", "span": [5, 10], "severity": "high" },
    { "entity_id": "E055", "type": "SSN", "text": "123-45-6789", "span": [16, 27], "severity": "high" }
  ],
  "triggered_combos": [
    { "comboId": "C021", "score": 4.5, "name": "First Name + Last Name" }
  ]
}

should_block is the single authoritative decision: your integration never needs its own blocking rules. Entity span values are character offsets into your input, so you can highlight or redact in place. applied_laws is always [] today; per-law evaluation has not shipped and we do not pretend otherwise.

Errors

One envelope, five codes.

{ "error": { "code": "rate_limited", "message": "...", "request_id": "b3f1..." } }
400

no_text / invalid_json

No text provided, or the body was not valid JSON

401

missing_api_key / invalid_api_key

No key, or an unrecognized key (when enforcement is on)

413

payload_too_large

Input above the 50,000-character limit

429

rate_limited

Per-minute or per-day limit exceeded (see Retry-After)

503

detector_unavailable

Upstream detector temporarily unavailable; retry

Good to know

Operational notes.

Typical detection response in one to five seconds. Detection is LLM-backed, not a regex pass.
Requests are capped at 50,000 characters. Chunk longer documents client-side.
Rate limits are per key, per minute and per day. A 429 includes a Retry-After header.
Detection runs in Canada (Toronto servers, Azure OpenAI in a Canadian region). Prompt content is processed transiently and not logged.
If the upstream detector cannot be reached, the API returns 503 rather than a silent empty result, so your integration can fail safe.

Want a key?

Keys are provisioned per organization. Tell us what you are building and we will set you up.

Request API access