API reference

One HTTP endpoint does the work: you send text or a file, you get it back with personal data replaced by stable tokens. Everything on this page is live — paste your workspace key and the Send buttons hit your real workspace.

not set

Stored in this tab only (sessionStorage), never sent anywhere except the request you fire. Get one from your dashboard → Integration, or create a workspace.

Overview

Base URL is https://piishield.ai. Requests and responses are JSON. There is no SDK to install — the whole integration is one POST.

no SDKJSON in, JSON out stable tokensmetered per request

Authentication

Your workspace key identifies the workspace, applies its coverage settings, and meters usage. Pass it as a query parameter:

POST https://piishield.ai/api/my/redact-document?key=shield_…
It is a credential. Keep it in your server's environment, never in client-side code or source control. Anyone holding it can spend your quota and read your workspace settings. Rotate it from the dashboard if it leaks.

How tokens work

Each distinct value becomes a numbered token — [EMAIL_1], [PERSON_2]. Within one request the same value always gets the same token, so a model can still reason about identity: “[PERSON_1] applied twice” is a true sentence about one person.

Send the whole payload in one call. One call is one redaction session. Splitting a record into per-field calls gives the same value a different token each time and destroys that property.

Redact text

POST/api/my/redact-document?key=…

The workhorse. Send any text — a record serialised as JSON, an interview note, a prompt — and get it back tokenised.

FieldTypeNotes
text required*stringThe content to redact. *Either text or base64.
filenamestringLabel only, used for your own logs.
mediaTypestringtext/plain for text.
alwaysMaskstring[]Values you already know are personal. See Declare known values.

Detect only — no rewriting

POST/api/my/redact-document?key=…+ preview: true

Returns every finding with a confidence and a plain-language reason, and changes nothing. Use it as a gate — “does a human need to look at this before the pipeline runs?” — without paying to rewrite a payload you may not send.

Redact a file

POST/api/my/redact-document?key=…+ base64

Word, Excel, PowerPoint, PDF, images and text/CSV/JSON/code. Office files come back as the same file with formatting intact; PDFs and scans are redacted in place using OCR. Hyperlink targets and document properties are redacted too — otherwise a CV can read [EMAIL_1] on the page while the underlying mailto: link still carries the address.

FieldTypeNotes
base64 required*stringThe file bytes, base64-encoded.
filename requiredstringThe extension decides how it is parsed.
mediaTypestringMIME type, when you know it.
allowSignatureInvalidationbooleanA digitally signed PDF is refused by default — redaction necessarily breaks the signature. Set true to redact a derivative anyway.

Response carries redactedFile (base64) and, for text-family inputs, redacted as a plain string.

Declare values you already know

POST/api/my/redact-document?key=…+ alwaysMask

Detection is a heuristic. The name detector is anchored on a list of given names, so it finds some names and misses others — and it will miss more outside the anglophone set. Guessing harder is not the fix: a name is just a word, and a looser detector starts masking employers.

But your system usually knows. candidate.first_name is a name, because your schema says so. Declare those values and they are masked deterministically, for this request only — never stored, never applied to another workspace.

Do this on CVs especially. Tested against a real CV without it: the email and phone were masked, and the candidate's own name, LinkedIn URL and GitHub handle all survived. With the candidate declared, all of it went.

Rules for one request only

POST/api/my/redact-document?key=…+ customPatterns

alwaysMask declares values; customPatterns declares a shape. Send the identifiers your system uses — an employee number, a claim reference, an internal case ID — and they are masked under your own label for this request, without being written into the workspace's saved coverage.

Each entry is {"type": "YOUR_LABEL", "pattern": "a JavaScript regular expression"}. Your label appears in the token, so EMP-445192 comes back as [EMPLOYEE_ID_1], not as a generic identifier. A pattern you declare outranks the built-in heuristics, because you know what your data is and we are guessing.

Rules that should apply to everyone belong in Coverage in the dashboard, where they are stored once and enforced on every surface. Use this for per-request or per-tenant rules your caller knows and we do not.


Workspace & quota

GET/api/my?key=…

Your workspace, its plan, coverage settings and remaining allowance. Use it for a health check on startup, or to show usage in your own UI.

Coverage catalogue

GET/api/metano key needed

Every detector PII Shield offers, grouped into categories, with a worked example for each. Public — this is what the dashboard and the browser extension both render, so your own settings UI can stay in step without hard-coding a list.

Leak Audit

GET/api/my/leaks?key=…

What has already reached AI, per seat. Values are encrypted to a key your workspace holds in its own browser — the API returns ••• and a ciphertext, never a readable value, and that is true for us as well as for you.


Errors

StatusMeaningWhat to do
400Malformed body, or neither text nor base64Fix the request.
402Out of quotaUpgrade or add credits. Do not fall back to sending raw data.
404Unknown keyCheck the key. Treat as fatal.
200 + supported:falseReadable but not processable — e.g. a signed PDFRead error and code. SIGNED_PDF needs allowSignatureInvalidation.
Fail closed. If a call errors, return an error to your caller — do not pass the raw record through. A logged warning that forwards the data turns a brief outage into an unnoticed disclosure of everything in your pipeline.

Limits & billing

One request is one billable unit regardless of size, which is another reason to send a whole record in one call rather than field by field. Plans and allowances are on your dashboard; the live figures are below.

GET/api/billing/plansno key needed

Recipes

Before you write any code: is there an MCP server already?

If the system holding the data ships an MCP server — most ATSs, CRMs and helpdesks now do — you may not need this API at all. Put the shield in front of that server and neither you nor the vendor writes an integration:

{ "mcpServers": { "ats": {
    "command": "node",
    "args": ["~/.claude/plugins/pii-shield/mcp-wrap.mjs",
             "--", "npx", "-y", "mcp-remote", "https://your-ats.example.com/mcp"],
    "env": { "PII_SHIELD_KEY": "shield_…" } } } }

Is your vendor's server remote or local? Most hosted ATS MCP servers — Greenhouse's among them — are https:// endpoints, not programs. There is nothing to spawn, so mcp-remote bridges the endpoint to stdio and the wrapper fronts the bridge; it handles the OAuth. A server that already runs locally needs no bridge — put its own command after the --. Take the URL from your vendor's documentation.

Point your client at the wrapper, not at the vendor URL. If you configure the vendor endpoint directly, your client opens that connection itself and the shield is never in the path — the connection works, records come back, and nothing looks wrong.

Tool results are redacted on the way to the model; the model's tokens are turned back into real values on the way out, so a follow-up find_by_email("[EMAIL_1]") still resolves. Tool names and schemas are untouched. It fails closed. Ships with the Claude Code plugin; see the guide.

Not doing the setup yourself? The guide has a copy-paste prompt that has Claude Code do it and then prove the redaction worked.

The wrapper needs a local process, so it works in Claude Code and Claude Desktop. A browser cannot start one — for claude.ai in a browser use the browser extension, or expose your own integration as a remote connector using this API.
Use the API instead when the data is in your own code — a pipeline, a job, a service, your own MCP server. The two are not alternatives, they bill to the same workspace and obey the same coverage.

Node — redact before an LLM call

const r = await fetch(`https://piishield.ai/api/my/redact-document?key=${process.env.PII_SHIELD_KEY}`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ text: JSON.stringify(record), filename: "record.json", mediaType: "text/plain" }),
});
const out = await r.json();
if (!r.ok || out.error) throw new Error("PII Shield unavailable — refusing to send raw data");
await claude.messages.create({ /* … */ content: out.redacted });

Python

import os, requests
r = requests.post(
    "https://piishield.ai/api/my/redact-document",
    params={"key": os.environ["PII_SHIELD_KEY"]},
    json={"text": text, "filename": "record.txt", "mediaType": "text/plain"},
    timeout=30,
)
r.raise_for_status()
safe = r.json()["redacted"]

curl

curl -s "https://piishield.ai/api/my/redact-document?key=$PII_SHIELD_KEY" \
  -H 'content-type: application/json' \
  -d '{"text":"call jane@acme.com","filename":"n.txt","mediaType":"text/plain"}'
Connecting an ATS or another MCP server? There is a full worked example with a Greenhouse-shaped MCP server and its tests in tools/ats-mcp-demo, plus a step-by-step integration guide. Ask us for the PDF, or see the product guide.