CLAUDE LABJP
BILLING — Day two after the Jun 15 change: Agent SDK, headless runs, GitHub Actions, and third-party agents now bill against separate monthly credits ($20/$100/$200) at full API rates with no rollover, making first-day cost measurements the basis for any reworkREGULATED — TCS partnered with Anthropic to bring Claude to banks, airlines, and other regulated industries, while DXC integrates Claude into the core systems those sectors rely onRETIRED — Sonnet 4 and Opus 4 left the API on Jun 15; confirm via your logs that scripts referencing them have moved to the latest generation such as Opus 4.8EXPORT — Claude Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); Anthropic says it is working to restore accessSAFE — Only the two new Mythos-class models are affected; every other model including Opus 4.8 keeps running normallySUBAGENTS — Claude Code sub-agents can spawn their own sub-agents up to five levels deep, widening the design space for multi-stage delegationBILLING — Day two after the Jun 15 change: Agent SDK, headless runs, GitHub Actions, and third-party agents now bill against separate monthly credits ($20/$100/$200) at full API rates with no rollover, making first-day cost measurements the basis for any reworkREGULATED — TCS partnered with Anthropic to bring Claude to banks, airlines, and other regulated industries, while DXC integrates Claude into the core systems those sectors rely onRETIRED — Sonnet 4 and Opus 4 left the API on Jun 15; confirm via your logs that scripts referencing them have moved to the latest generation such as Opus 4.8EXPORT — Claude Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); Anthropic says it is working to restore accessSAFE — Only the two new Mythos-class models are affected; every other model including Opus 4.8 keeps running normallySUBAGENTS — Claude Code sub-agents can spawn their own sub-agents up to five levels deep, widening the design space for multi-stage delegation
Articles/API & SDK
API & SDK/2026-06-16Advanced

PII Masking for Claude API Lives or Dies on the Ledger — Restore, Encrypt, Measure

The hard part of masking PII before Claude API isn't detection — it's operating the token ledger you restore from. Encrypted storage, multi-instance sharing, and a daily leak-rate loop, with working code.

claude-api62piisecurity9production93privacy3observability11

Premium Article

Few people argue with the idea of masking PII before it reaches the Claude API. The trouble starts right after. Once you've built a detector from regexes and an NER model, you're left with operational questions: how do you put the masked values back, where does that mapping live, and how do you prove nothing leaked? This is where the design suddenly gets hard.

The detection logic comes together in a few days. What hurts in production is almost never detection — it's the handling of the restore ledger and the absence of a way to keep measuring leak rate. Across the business assistants I've run on the Claude API as an indie developer, nearly every review finding and incident traced back to those two things. This article walks through building a detector, but puts its weight on ledger operations and continuous measurement, with the implementation code to match.

Think in round trips, not one-way trips

If you treat masking as "strip it before sending," you will break every summarization or assistant use case. When Claude replies with Contacted <PERSON_001>, that string is meaningless to the user until you turn <PERSON_001> back into Taro Tanaka. Masking is not a one-way send-time step — it's a round trip where masking (outbound) and restoration (inbound) are a matched pair.

Seen as a round trip, the protagonist is not the detector but the ledger that maps tokens to originals. The ledger has to satisfy three things at once.

First, it must be consistent across turns and requests. The same Taro Tanaka must always get the same <PERSON_001>, or Claude can't tell it's the same person and output quality degrades. Second, it must be encrypted. The ledger is literally a map of which token is whom, so a leaked ledger is worse than leaked raw PII. Third, it must be shareable across instances, because production never runs in a single process.

If you only need irreversible masking (collapse everything to <PERSON>), you don't need a ledger at all. The ledger exists only for restoration. That's why "do we default to reversible?" is the first fork that decides your operational cost. When I'm unsure whether restoration will be needed, I choose reversible: dropping to irreversible later is one line, but going from irreversible back to reversible is impossible because the original is already gone.

Two detection layers are enough — Luhn-filtered regex and a Haiku NER

Before the ledger, lock down detection in its minimal form. In my deployments, two layers were plenty: regex (Layer 1: identifiers) and a lightweight-model NER (Layer 2: quasi-identifiers). Layer 3, the kind that needs contextual inference, I don't try to machine-process — I push it to input guidance in the UI.

The key in the regex layer is to filter card-like digit runs through a Luhn check. Catching "13+ digits" alone sweeps up ISBNs, JANs, and tracking numbers, and summarization accuracy visibly drops.

// pii-detect.ts — Layer 1 (identifiers). Tokens use a uniform <CATEGORY_NNN> shape
// Rationale: false positives hurt model quality, misses hurt privacy. Luhn curbs card false positives
export type Span = { start: number; end: number; category: string; text: string };
 
const REGEXES: Record<string, RegExp> = {
  EMAIL: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
  PHONE_JP: /(?:\+?81[-\s]?|0)\d{1,4}[-\s]?\d{1,4}[-\s]?\d{3,4}/g,
  CREDIT_CARD: /\b(?:\d[ -]*?){13,19}\b/g,
};
 
export function detectLayer1(input: string): Span[] {
  const spans: Span[] = [];
  for (const [category, regex] of Object.entries(REGEXES)) {
    for (const m of input.matchAll(regex)) {
      const text = m[0];
      if (category === "CREDIT_CARD" && !isLuhnValid(text.replace(/[ -]/g, ""))) continue;
      spans.push({ start: m.index!, end: m.index! + text.length, category, text });
    }
  }
  return spans;
}
 
function isLuhnValid(num: string): boolean {
  if (!/^\d{13,19}$/.test(num)) return false;
  let sum = 0, alt = false;
  for (let i = num.length - 1; i >= 0; i--) {
    let n = parseInt(num[i], 10);
    if (alt) { n *= 2; if (n > 9) n -= 9; }
    sum += n; alt = !alt;
  }
  return sum % 10 === 0;
}

Names and addresses won't fall to regex. Here I use Claude Haiku as an NER. Since the NER input also contains PII, keeping it inside a Claude call I control is easier to justify in an audit than shipping it to a generic cloud NER. The crucial part is to not over-trust the output and to pin a fixed model version (see pitfall 3 below).

// pii-ner.ts — Layer 2 (quasi-identifiers: names, addresses). Pin the model to a dated ID
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
 
const NER_MODEL = "claude-haiku-4-5-20251001"; // dated ID to freeze behavior
const NER_SYSTEM = `You are a Japanese named-entity extractor. Extract only PERSON and ADDRESS_JP spans
from the input and return strictly this JSON. No guessing, no invention. Omit anything you cannot classify.
{"entities":[{"text":"string","category":"PERSON"|"ADDRESS_JP"}]}`;
 
export async function detectLayer2(input: string): Promise<{ text: string; category: string }[]> {
  const res = await client.messages.create({
    model: NER_MODEL, max_tokens: 1024, system: NER_SYSTEM,
    messages: [{ role: "user", content: input }],
  });
  const text = res.content.filter((b) => b.type === "text").map((b: any) => b.text).join("");
  const match = text.match(/\{[\s\S]*\}/);
  if (!match) return []; // nothing extracted -> rely on the rescan to catch misses
  try {
    const parsed = JSON.parse(match[0]) as { entities?: { text: string; category: string }[] };
    return (parsed.entities ?? []).filter((e) => e.text && ["PERSON", "ADDRESS_JP"].includes(e.category));
  } catch {
    return []; // on parse failure, skip and let measurement catch the leak
  }
}

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Encrypting the restore ledger with AES-GCM and sharing it across instances
Quantifying leak rate every day with a golden dataset and shadow rescan
Killing the three operational bugs: stream splitting, token translation, NER drift
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-05-10
What metadata.user_id in the Claude API Is Actually For — Designing the Abuse-Detection vs. Privacy Trade-off
The metadata.user_id field in the Messages API exists to sharpen abuse detection, but sending raw email addresses creates a privacy problem. Here is the HMAC-based stable pseudo-ID pattern I use, plus a clear set of rules for when to send it and when not to.
API & SDK2026-05-01
Claude API Telemetry on ClickHouse: A Production Guide to Cost, Latency, and Error Analytics
Stream per-request Claude API telemetry into ClickHouse, build sub-second dashboards with materialized views, and detect cost spikes, retry loops, and silent failures with practical SQL recipes.
API & SDK2026-04-30
Building a Production Claude API Pipeline on Cloudflare Queues: Fault Tolerance, Backpressure, and Cost Control
A practical, code-first walkthrough for routing Claude API calls through Cloudflare Queues — covering producer/consumer code, retry-vs-DLQ branching, priority lanes, and token budgeting for production workloads.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →