Learn how to automatically find credit card numbers, CVV security codes, and expiration dates hidden in text, chat transcripts, support tickets, and application logs using PII Detection API — with Luhn validation, card network intelligence, and context-aware AI that keeps false positives out of your PCI DSS scope.
Credit card numbers are among the most dangerous pieces of sensitive data an organization can accidentally retain. A single primary account number (PAN) sitting in a chat transcript, a debug log, or a customer support ticket can pull an entire system into PCI DSS audit scope, trigger breach notification obligations, and expose real financial harm to a real person. Unlike a name or an email address, a leaked card number is directly monetizable by attackers — which is why card data commands some of the highest prices on illicit markets and why the payment industry regulates it so strictly.
PII Detection API detects three distinct cardholder data elements as separate entity types: CREDIT_CARD_NUMBER for the PAN itself, CVV_NUMBER for the three- or four-digit card verification value, and CREDIT_CARD_EXPIRATION_DATE for expiry dates. Each detection returns the entity type, the exact matched text, precise character offsets, and a confidence score, so you know not just that card data exists in a document but exactly where it is. You can optionally receive a masked version of the input in the same response — useful when the next step after discovery is remediation.
The engine combines the classic structural signals — digit-length patterns, issuer identification number (IIN) ranges, and the Luhn checksum — with transformer-based contextual analysis. That combination matters. Structural checks alone flag every Luhn-valid 16-digit string, including device IMEIs and plenty of order numbers; contextual AI understands that "your card ending 4242" in a refund conversation is cardholder data while "shipment tracking 4556737586899855" is not. The result is high recall on real card data with a dramatically lower false-positive rate than regex-plus-Luhn scanners.
The Payment Card Industry Data Security Standard (PCI DSS) applies to every system that stores, processes, or transmits cardholder data — and crucially, that includes systems where card data lands unintentionally. Merchants routinely discover that customers have typed full card numbers into live-chat widgets, emailed them to support teams, or dictated them into call transcripts. The moment a PAN hits your ticketing system, your log aggregator, or your data warehouse, that system is in scope for PCI DSS unless the data is removed or rendered unreadable.
PCI DSS Requirement 3 governs stored account data. The PAN must be rendered unreadable wherever it is stored (via truncation, tokenization, hashing, or strong encryption), and sensitive authentication data — including the CVV — may never be stored after authorization, not even encrypted. A CVV discovered at rest in any system is an immediate compliance failure that must be remediated, not merely protected. Requirement 12 and the standard's scoping guidance also expect organizations to confirm, at least annually, that cardholder data does not exist outside the defined cardholder data environment (CDE). That confirmation is exactly what automated card detection provides: sweep your tickets, logs, and data stores through the detection API, and you have evidence-grade findings with locations and confidence scores.
Critical: If a scan finds a CVV_NUMBER in stored data — a ticket archive, a database, a log file — treat it as an incident, not a finding. PCI DSS prohibits storing card verification values after authorization under any circumstances. Delete or redact it immediately and fix the intake path that captured it.
Beyond PCI DSS, card numbers are personal data under GDPR and personal information under CCPA/CPRA, and most U.S. state breach notification statutes treat "financial account number in combination with access code" as notification-triggering data. Detecting and removing stray card data therefore reduces exposure under multiple regimes at once.
A payment card number is not a random string of digits — it has a defined structure under ISO/IEC 7812, and understanding that structure explains both how detection works and why naive approaches fail.
Card numbers range from 13 to 19 digits depending on the network, with 16 being by far the most common. In real-world text they appear in many shapes: grouped in blocks of four ("4556 7375 8689 9855"), hyphenated ("4556-7375-8689-9855"), as an unbroken digit run ("4556737586899855"), or in the American Express 4-6-5 grouping ("3782 822463 10005"). A production-grade detector must treat all of these as the same underlying entity, and PII Detection API normalizes separators before validation so formatting never hides a PAN.
Partial numbers matter too. Text like "the Visa ending in 9855" or a properly truncated "455673******9855" is not a full PAN and is deliberately not flagged as CREDIT_CARD_NUMBER — truncation to first-six/last-four is precisely the PCI-approved display format, and flagging it would bury your team in noise. The engine distinguishes a truncated display form from a complete, Luhn-valid account number.
The Luhn algorithm (ISO/IEC 7812-1) is a simple checksum designed in the 1950s by IBM engineer Hans Peter Luhn to catch transcription errors — a mistyped digit or two adjacent digits swapped. Every valid payment card number passes it, which makes it a powerful first-pass filter for detection: any 16-digit candidate that fails Luhn is definitely not a card number.
Worked example with the well-known test number 4242 4242 4242 4242: doubling every second digit from the right turns each "4" in the doubled positions into 8, giving the sequence 8, 2, 8, 2, 8, 2, 8, 2, 8, 2, 8, 2, 8, 2, 8, 2. The sum is 80, which is divisible by 10 — the number passes. Change any single digit and the sum shifts, breaking divisibility; that is the error-detection property working as designed.
It is equally important to understand what Luhn does not tell you. Passing the checksum means a number is structurally plausible, not that it is a real, issued, active card. Roughly one in ten random digit strings of a given length passes Luhn by pure chance, and some non-card identifiers — most notably device IMEI numbers — use the Luhn checksum themselves. This is exactly why PII Detection API layers contextual AI on top of the checksum instead of stopping there: structure gets a candidate through the door, context decides whether it is actually cardholder data.
Tip: Because the API performs Luhn validation internally, you never need to pre-filter text yourself. Send raw tickets, logs, or transcripts and let the engine surface only checksum-valid, contextually confirmed card numbers.
Each card network reserves specific IIN prefixes and lengths. Detection engines use these ranges both to confirm candidates and to enrich findings — knowing that a detected PAN is an Amex (15 digits, no fourth group) versus a Visa (16 digits) changes how a masked value should be rendered and can matter for incident triage. The major networks break down as follows:
| Network | IIN Prefixes | Length | Example (test number) |
|---|---|---|---|
| Visa | 4 |
13, 16, 19 | 4111 1111 1111 1111 |
| Mastercard | 51–55, 2221–2720 |
16 | 5555 5555 5555 4444 |
| American Express | 34, 37 |
15 | 3782 822463 10005 |
| Discover | 6011, 644–649, 65 |
16, 19 | 6011 1111 1111 1117 |
| JCB | 3528–3589 |
16–19 | 3530 1113 3330 0000 |
| Diners Club | 300–305, 36, 38–39 |
14–19 | 3056 9309 0259 04 |
| UnionPay | 62 |
16–19 | 6200 0000 0000 0005 |
Two practical notes on this table. First, the Mastercard 2221–2720 range was activated in 2016, and older regex patterns that only match the classic 51–55 prefixes silently miss millions of newer Mastercards — a common blind spot in home-grown scanners. Second, 19-digit PANs (common for UnionPay and some Visa and Discover ranges) break scanners hard-coded to 16 digits. PII Detection API maintains current network ranges so your detection coverage does not decay as the card ecosystem evolves. You can browse the full catalog of financial entity types on our entities page.
Detecting card data takes one API call. Send text to the endpoint with the three cardholder data entity types, and the response tells you exactly what was found, where, and with what confidence — plus an optional masked copy of the input. Try it live in the interactive demo before writing any code.
curl -X POST https://piidetectionapi.com/api/moderate.php \ -H "Content-Type: application/json" \ -d '{ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "Customer paid with card 4556 7375 8689 9855, exp 09/27, CVV 613.", "entities": ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "CREDIT_CARD_EXPIRATION_DATE"], "mask_mode": "replace" }'
import requests resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "Customer paid with card 4556 7375 8689 9855, exp 09/27, CVV 613.", "entities": ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "CREDIT_CARD_EXPIRATION_DATE"], "mask_mode": "replace", }, timeout=30, ) data = resp.json() for e in data["detected_entities"]: print(e["type"], e["text"], e["start"], e["end"], e["confidence"])
const resp = await fetch("https://piidetectionapi.com/api/moderate.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: "YOUR_API_KEY", api_type: "pii_detection", text: "Customer paid with card 4556 7375 8689 9855, exp 09/27, CVV 613.", entities: ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "CREDIT_CARD_EXPIRATION_DATE"], mask_mode: "replace" }) }); const data = await resp.json(); data.detected_entities.forEach(e => console.log(e.type, e.text, e.start, e.end, e.confidence) );
The response contains every detected entity with character-level offsets, a count, timing metadata, and the masked text:
{
"detected_entities": [
{"type": "CREDIT_CARD_NUMBER", "text": "4556 7375 8689 9855", "start": 24, "end": 43, "confidence": 0.99},
{"type": "CREDIT_CARD_EXPIRATION_DATE", "text": "09/27", "start": 49, "end": 54, "confidence": 0.96},
{"type": "CVV_NUMBER", "text": "613", "start": 60, "end": 63, "confidence": 0.94}
],
"anonymized_text": "Customer paid with card [CREDIT_CARD_NUMBER], exp [CREDIT_CARD_EXPIRATION_DATE], CVV [CVV_NUMBER].",
"entities_detected": 3,
"processing_time_ms": 184,
"mask_mode_used": "replace",
"status": 200
}
Each request accepts up to 50,000 characters, so a full support ticket thread or a sizeable log chunk fits in a single call. If you omit the entities array entirely, the API scans for all 150+ entity types at once — useful for a broad first sweep before narrowing to cardholder data. See pricing for volume tiers, and the API reference for every request field.
A PAN alone is dangerous; a PAN with its expiration date and CVV is a complete card-not-present transaction kit. That is why PCI DSS treats the CVV as sensitive authentication data with an absolute post-authorization storage ban, and why detecting these companion elements is as important as detecting the number itself.
A CVV is just three digits (four for American Express) with no checksum and no distinctive structure. In isolation, "613" is indistinguishable from an area code, a quantity, or a room number. Pure pattern matching therefore cannot detect CVVs without flagging nearly every short number in your corpus. Context is everything: the engine looks for the linguistic and positional signals that mark a short digit string as a card verification value — labels like "CVV", "CVC", "CID", "security code", or "the 3 digits on the back", and proximity to a detected PAN or expiry date. In a call transcript that reads "…and the code on the back? Six one three. Great, processing your payment…", contextual AI connects the dictated digits to the payment flow even though the literal token "CVV" never appears.
Expiry dates arrive as "09/27", "09/2027", "Sept 2027", "exp. 09-27", or transcribed speech like "expires September twenty twenty-seven". The CREDIT_CARD_EXPIRATION_DATE entity captures these forms while using context to avoid mislabeling ordinary dates — "meeting on 09/27" is a calendar reference, not card data, and the model treats it accordingly. Detected alongside a PAN, an expiry materially raises the severity of a finding, so keeping it as a separate entity type lets your remediation workflow score exposure levels: PAN alone, PAN + expiry, or the worst case, PAN + expiry + CVV.
Remember: Expiration dates and cardholder names may be stored if protected, but the CVV may never be retained after authorization — encrypted or not. If your detection sweep surfaces CVV_NUMBER entities in any data store, remediation means deletion, and the capture path (an over-verbose logger, an unfiltered chat archive) needs fixing at the source.
The central engineering problem in card detection is not finding 16-digit numbers — it is not flagging the ones that are something else. Modern systems are saturated with long digit strings, and a scanner that cries wolf trains your team to ignore it.
The trickiest false positives pass the checksum. Device IMEI numbers are 15 digits and use the very same Luhn algorithm — a telecom support log full of IMEIs will light up a naive regex-plus-Luhn scanner with "American Express" hits. Roughly 10% of arbitrary numeric IDs (order numbers, container codes, loyalty IDs) also pass Luhn coincidentally. PII Detection API resolves these with context: an IMEI sits next to words like "device", "handset", or "serial"; an order ID follows "order #" or lives in a URL path. The transformer model reads those cues the way a human reviewer would.
4111 1111 1111 1111 and 4242 4242 4242 4242 are published, universally known test PANs that appear legitimately in developer docs, QA fixtures, and integration logs.If you are scanning engineering content where documented test cards are expected and harmless, the custom_instruction field lets you exclude them in plain language while still catching real card data:
curl -X POST https://piidetectionapi.com/api/moderate.php \ -H "Content-Type: application/json" \ -d '{ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "QA used 4242 4242 4242 4242 but the customer'"'"'s real card 5425 2334 3010 9903 leaked into this log.", "entities": ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "CREDIT_CARD_EXPIRATION_DATE"], "custom_instruction": "Ignore well-known public test card numbers such as 4111111111111111 and 4242424242424242.", "threshold": 0.7 }'
import requests resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "QA used 4242 4242 4242 4242 but the customer's real card 5425 2334 3010 9903 leaked into this log.", "entities": ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "CREDIT_CARD_EXPIRATION_DATE"], "custom_instruction": "Ignore well-known public test card numbers such as 4111111111111111 and 4242424242424242.", "threshold": 0.7, }, timeout=30, ) print(resp.json()["detected_entities"]) # Only the real Mastercard 5425 2334 3010 9903 is reported
const resp = await fetch("https://piidetectionapi.com/api/moderate.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: "YOUR_API_KEY", api_type: "pii_detection", text: "QA used 4242 4242 4242 4242 but the customer's real card 5425 2334 3010 9903 leaked into this log.", entities: ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "CREDIT_CARD_EXPIRATION_DATE"], custom_instruction: "Ignore well-known public test card numbers such as 4111111111111111 and 4242424242424242.", threshold: 0.7 }) }); const data = await resp.json(); console.log(data.detected_entities); // Only the real Mastercard 5425 2334 3010 9903 is reported
Raising the threshold parameter (for example to 0.7 or 0.8) is the complementary lever: it suppresses low-confidence candidates in extremely noisy numeric data like machine logs, at the cost of slightly lower recall. For compliance sweeps where a missed PAN is worse than a reviewed false positive, keep the default 0.5 and triage by confidence score instead.
PCI DSS distinguishes between cardholder data, which may be stored with protection, and sensitive authentication data, which may not be stored after authorization at all. Mapping each detected entity type to its regulatory treatment turns raw detection output into a compliance action plan:
| Data Element | API Entity Type | Storage Permitted? | Required Treatment |
|---|---|---|---|
| Primary Account Number (PAN) | CREDIT_CARD_NUMBER |
Yes, if business-justified | Render unreadable: truncate, tokenize, hash, or encrypt (Req. 3) |
| Expiration date | CREDIT_CARD_EXPIRATION_DATE |
Yes | Protect when stored with the PAN |
| Cardholder name | PERSON_NAME |
Yes | Protect when stored with the PAN |
| Card verification value (CVV/CVC/CID) | CVV_NUMBER |
Never after authorization | Must not be retained — delete on discovery |
PCI assessments are scoped to wherever cardholder data lives. Every system you can prove is free of card data is a system your assessor can rule out of scope — which shrinks audit cost, shortens your Self-Assessment Questionnaire, and reduces the attack surface that matters. Running automated detection across ticket archives, log pipelines, and data warehouses gives you exactly that proof, on a schedule, with findings you can remediate before an assessor (or an attacker) finds them first. Many teams wire detection into their log shippers and chat platforms so PANs are masked in-flight and never persist at all — the strongest scope-reduction posture available. For the full compliance picture, see our guide to PCI DSS cardholder data discovery.
Good to know: Scanning text through PII Detection API does not expand your PCI scope the way persisting card data does — the API processes requests transiently and is designed for exactly this discovery-and-redaction role. For maximum control, the engine is also available as an on-premise deployment that keeps every byte inside your own CDE boundary.
Always request CREDIT_CARD_NUMBER, CVV_NUMBER, and CREDIT_CARD_EXPIRATION_DATE in the same call. Findings that cluster — a PAN with its expiry and CVV within a few dozen characters — represent complete stolen-card kits and deserve the highest remediation priority. The character offsets in the response make proximity analysis trivial.
[CREDIT_CARD_NUMBER]-style placeholder. Best for support tickets and transcripts that humans still need to read.Retroactive sweeps are essential, but the cheapest PAN to remediate is the one that never lands. Insert a detection call into chat ingestion, ticket creation, and log shipping so card data is masked before storage. At typical response times under 200 ms, the API fits synchronous paths for chat and ticketing, and batch paths for log pipelines.
A detection pipeline that writes matched card numbers into its own results log has just created a new PCI-scoped system. Persist entity types, offsets, confidence scores, and document identifiers — not the matched text. If reviewers need to see findings, show them the anonymized_text.
Card data leaks continuously — every new integration, log statement, and support macro is a fresh opportunity. Quarterly (or better, continuous) scans across your ticket archive, message history, and warehouse free-text columns align with PCI DSS's expectation that organizations periodically confirm no cardholder data exists outside the CDE. Combine cardholder entities with SSN detection and name detection in the same pass to cover PCI, GLBA, and privacy-law obligations in a single sweep.
Yes. Every CREDIT_CARD_NUMBER candidate is checksum-verified before being reported, alongside IIN-range and length validation for the major networks. Luhn confirms structural plausibility only — it does not (and cannot) verify that a card is issued or active, and no such live check is performed. The combination of checksum, network ranges, and surrounding context is what produces the confidence score in each finding.
By default, yes — they are structurally valid Visa numbers, and in a production data store even a test number usually indicates a process worth reviewing. If you are scanning developer documentation or QA fixtures where test PANs are expected, add a custom_instruction such as "ignore well-known public test card numbers" and the engine will skip them while still reporting real card data.
Yes. The engine normalizes spaces, hyphens, dots, and mixed separators before validation, so 4556737586899855, 4556-7375-8689-9855, and 4556 7375 8689 9855 are all detected as the same PAN. Amex's 4-6-5 grouping and 13- to 19-digit lengths for other networks are handled as well, including numbers split across line breaks in wrapped text such as emails.
Detection is how you shrink scope, not grow it. The API processes text transiently and returns findings without persisting card data; your responsibility is to avoid logging matched PANs on your side — store offsets and types instead. Systems you scan and clean (or protect in-flight) become systems you can demonstrate are out of scope. An on-premise deployment is available if card-adjacent text must never leave your environment.
Last-four references and properly truncated PANs (first six/last four) are the PCI-approved display format and are not flagged as CREDIT_CARD_NUMBER — flagging them would flood real findings with compliant noise. The engine specifically distinguishes a complete, Luhn-valid account number from a truncated display form, even when the truncated form sits in payment-related context.
Debit and prepaid cards issued on the Visa, Mastercard, Amex, Discover, JCB, Diners, and UnionPay networks follow the same IIN and Luhn structure as credit cards and are detected identically — PCI DSS draws no distinction between them. A small number of legacy or closed-loop card ranges that do not use Luhn are handled through network-range knowledge and context rather than checksum alone.
Typical processing time is under 200 ms per request (reported in the processing_time_ms field), with up to 50,000 characters per call. For archives, chunk documents to that limit and parallelize requests; the hash mask mode keeps repeated card numbers consistent across chunks so you can deduplicate findings. See pricing for throughput tiers.
Test credit card, CVV, and expiration date detection on your own sample text in seconds — no signup required for the live demo.
Try the Live Demo View Pricing