Skip to main content
Standalone endpoint for masking PII and secrets in text. Use this endpoint when you need to mask text independently of LLM provider proxies.
POST /api/mask

Use Cases

  • Browser extensions that need to mask clipboard content before pasting
  • CLI tools that pre-process prompts
  • Custom integrations that handle their own LLM communication
  • Multi-turn conversations where you need to maintain placeholder numbering

Request

curl -X POST http://localhost:3000/api/mask \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Contact john@example.com or call 555-1234"
  }'

Parameters

ParameterTypeRequiredDescription
textstringYesText to scan and mask
languagestringNoLanguage code for PII detection (auto-detected if not provided)
startFromobjectNoCounter values to continue numbering from previous calls
detectarrayNoWhat to detect: ["pii"], ["secrets"], or ["pii", "secrets"] (default: both)

Response

{
  "masked": "Contact [[EMAIL_ADDRESS_1]] or call [[PHONE_NUMBER_1]]",
  "context": {
    "[[EMAIL_ADDRESS_1]]": "john@example.com",
    "[[PHONE_NUMBER_1]]": "555-1234"
  },
  "counters": {
    "EMAIL_ADDRESS": 1,
    "PHONE_NUMBER": 1
  },
  "entities": [
    { "type": "EMAIL_ADDRESS", "placeholder": "[[EMAIL_ADDRESS_1]]" },
    { "type": "PHONE_NUMBER", "placeholder": "[[PHONE_NUMBER_1]]" }
  ],
  "language": "en",
  "languageFallback": false
}
FieldDescription
maskedText with PII/secrets replaced by placeholders
contextMapping of placeholder to original value (for client-side unmasking)
countersFinal counter values per entity type
entitiesList of detected entities with their placeholders
languageLanguage used for PII detection
languageFallbackWhether the configured fallback language was used (auto-detection failed)

Detection Options

Control what gets detected using the detect parameter:
curl -X POST http://localhost:3000/api/mask \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Email john@example.com",
    "detect": ["pii"]
  }'

Multi-Turn Support

For conversations spanning multiple requests, use the counters from the response as startFrom in the next request to maintain consistent numbering: First request:
curl -X POST http://localhost:3000/api/mask \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Contact john@example.com"
  }'
Response:
{
  "masked": "Contact [[EMAIL_ADDRESS_1]]",
  "counters": { "EMAIL_ADDRESS": 1 },
  ...
}
Second request (continue numbering):
curl -X POST http://localhost:3000/api/mask \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Also reach out to jane@example.com",
    "startFrom": { "EMAIL_ADDRESS": 1 }
  }'
Response:
{
  "masked": "Also reach out to [[EMAIL_ADDRESS_2]]",
  "counters": { "EMAIL_ADDRESS": 2 },
  ...
}

Client-Side Unmasking

The context field provides a mapping for unmasking responses on the client side:
function unmask(text, context) {
  let result = text;
  for (const [placeholder, original] of Object.entries(context)) {
    result = result.replaceAll(placeholder, original);
  }
  return result;
}

// Usage
const response = await fetch('/api/mask', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: 'Email john@example.com' })
});
const { masked, context } = await response.json();

// Later, unmask an LLM response
const llmResponse = "I'll contact [[EMAIL_ADDRESS_1]] right away.";
const unmasked = unmask(llmResponse, context);
// "I'll contact john@example.com right away."

Error Responses

Validation Error (400)

{
  "error": {
    "message": "Invalid request",
    "type": "validation_error",
    "details": [
      { "path": "text", "message": "text is required" }
    ]
  }
}

Detection Error (503)

Returned when Presidio or secrets detection is unavailable:
{
  "error": {
    "message": "PII detection failed",
    "type": "detection_error",
    "details": [
      { "message": "Failed to connect to Presidio..." }
    ]
  }
}