Sensitive Data Handling

Ma Mahalakshmi V Updated 19 Sep 2026
7 min read ·Lesson 160 of 224

Handling sensitive data in AI workflows

Sensitive data — personally identifiable information (PII) such as names, email addresses, phone numbers, government ID numbers, and payment details — flows through AI applications constantly: a customer support assistant reads a customer's account details, a document-summarization tool processes contracts full of names and addresses, a data-analysis assistant queries a database of user records. This lesson covers the specific risks that arise when sensitive data reaches a prompt, and the concrete techniques for reducing exposure without breaking the functionality that needs the data in the first place.

Why sending raw PII to a model call is a distinct risk

Every request you send through the API leaves your own infrastructure and is processed by OpenAI's systems. That is true of any third-party API call, but it matters specifically for PII because of a few compounding factors:

  • Data minimization principles — found in regulations like GDPR and CCPA, and simply good practice regardless of jurisdiction — hold that you should only transmit and retain the minimum personal data necessary for a given purpose. Sending a customer's full record when only their subscription tier is relevant violates this principle even if the transmission itself is otherwise secure.
  • Retention and processing terms differ from your own systems. Your own database has retention and access policies you control directly. A third-party API call is governed by that provider's data usage policies, which may differ from what you need for compliance with a specific regulation or a specific customer contract.
  • Prompts and completions can end up in logs (yours, and potentially the provider's, depending on API tier and settings) — which multiplies the number of places sensitive data physically exists, and therefore the number of places it could leak from.
  • The model may echo sensitive data back into its output, which then flows into whatever your application does with that output — displaying it, storing it, logging it — extending exposure to systems that had no reason to touch the raw data at all.

None of this means you can never process PII through the API — plenty of legitimate applications need to. It means you should default to sending the least sensitive representation of the data that still lets the model do its job.

Redaction before the prompt is built

The most direct technique is redacting or masking sensitive substrings before they ever enter the prompt, and — where the workflow requires it — reinserting the real values afterward using a mapping your own code controls, so the model itself never sees the real value.

import re

REDACTION_PATTERNS = {
    "EMAIL": re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"),
    "PHONE": re.compile(r"\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b"),
    "SSN": re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
    "CREDIT_CARD": re.compile(r"\b(?:\d{4}[-\s]?){3}\d{4}\b"),
}


def redact(text: str) -> tuple[str, dict[str, str]]:
    """
    Replaces sensitive substrings with placeholder tokens.
    Returns the redacted text and a mapping of token -> original value,
    so the caller can restore the real values later if needed.
    """
    mapping: dict[str, str] = {}
    counter = 0
    redacted = text

    for label, pattern in REDACTION_PATTERNS.items():
        def replace_match(match, label=label):
            nonlocal counter
            counter += 1
            token = f"[{label}_{counter}]"
            mapping[token] = match.group(0)
            return token

        redacted = pattern.sub(replace_match, redacted)

    return redacted, mapping


def restore(text: str, mapping: dict[str, str]) -> str:
    """Reinserts original values for any placeholder tokens still present."""
    restored = text
    for token, original in mapping.items():
        restored = restored.replace(token, original)
    return restored

redact scans the input text against a set of known PII patterns and replaces each match with a labeled placeholder token ([EMAIL_1], [PHONE_1], and so on), while remembering the real value behind each token in mapping. The model then sees only the redacted version — it can reason about the structure of the text ("this customer provided their email and phone number") without ever seeing the actual email or phone number. If your workflow needs the real values back in the final output (for example, generating a formatted letter that must contain the actual customer email), restore reverses the substitution using the mapping your own code held onto the whole time — the model never needed the real value to do its part of the job.

def test_redact_masks_email_and_phone():
    text = "Contact John at john.doe@example.com or 555-123-4567."
    redacted, mapping = redact(text)
    assert "john.doe@example.com" not in redacted
    assert "555-123-4567" not in redacted
    assert "[EMAIL_1]" in redacted
    assert "[PHONE_1]" in redacted
    print("PASS: redact masks email and phone substrings")


def test_restore_reverses_redaction():
    text = "Reach out to jane@example.com about the account."
    redacted, mapping = redact(text)
    restored = restore(redacted, mapping)
    assert restored == text
    print("PASS: restore reconstructs the original text from the mapping")


test_redact_masks_email_and_phone()
test_restore_reverses_redaction()

Using structured outputs to avoid unnecessary echoing

A second, complementary technique addresses a different part of the problem: even when the model legitimately needs some sensitive input (say, verifying that a provided email matches an account on file), you don't want the model repeating that sensitive value back inside free-form prose in its output, because free-form output is harder to control and more likely to end up displayed or logged somewhere unintended. Constraining the model to a structured response — as covered elsewhere in this course for structured outputs generally — lets you define a schema that simply doesn't include a field for echoing raw PII back.

from pydantic import BaseModel


class AccountLookupResult(BaseModel):
    account_found: bool
    subscription_tier: str | None
    # Deliberately no field for echoing the customer's email or phone back —
    # the caller already has that data and doesn't need the model to repeat it.


def build_lookup_request(client, redacted_customer_text: str):
    return client.responses.parse(
        model="gpt-5.6-terra",
        input=(
            "Given the following redacted customer message, determine "
            "whether it looks like a request for account status, and "
            f"nothing else:\n\n{redacted_customer_text}"
        ),
        text_format=AccountLookupResult,
    )

By constraining the output shape with text_format, you remove the opportunity for the model to include a raw PII value in its response, because the schema has nowhere for it to go. This is a stronger guarantee than a prompt instruction like "don't repeat the customer's email," because it's enforced by the response's structure rather than by the model choosing to comply.

Note: Redaction patterns like the ones above are a practical baseline, not a formally complete PII detector. Regexes will miss unusual formats (international phone numbers, names, addresses) and can occasionally over-match. For applications with strict compliance requirements, pair this approach with a dedicated PII-detection library or service, and treat the regex-based approach shown here as one layer of a broader data-handling strategy rather than a complete solution by itself.

Deciding what actually needs to reach the model

Before reaching for redaction, ask a more basic question: does the model need the sensitive field at all? Many workflows send an entire customer record to the model when only one or two fields are relevant to the task. Trimming the payload down to exactly what's needed is a simpler and more reliable form of data minimization than redacting a large blob after the fact.

def build_minimal_context(customer_record: dict, task: str) -> dict:
    """Extracts only the fields relevant to a given task, dropping the rest."""
    field_requirements = {
        "shipping_status": ["order_id", "shipping_status", "estimated_delivery"],
        "subscription_info": ["subscription_tier", "renewal_date"],
    }
    needed_fields = field_requirements.get(task, [])
    return {k: v for k, v in customer_record.items() if k in needed_fields}

For a shipping_status task, this function drops the customer's name, email, payment details, and anything else not explicitly listed — none of it was ever going to be sent to the model in the first place, which is strictly safer than sending everything and redacting afterward, since data that was never transmitted cannot leak from the transmission.

Common Mistakes

  • Redacting only the "obvious" fields and missing formats your regex doesn't cover. International phone numbers, alternate SSN formats, and names are notoriously hard to catch with simple patterns — treat regex redaction as a baseline, not a guarantee, especially for regulated data.
  • Sending an entire database record when only one field is relevant. This is the most common and most avoidable source of unnecessary PII exposure — trimming the payload (as in build_minimal_context) is often more effective than redaction after the fact.
  • Letting the model's free-form output become the only place sensitive values are checked or handled, instead of constraining output structure so there's no field for that data to occupy in the first place.

Best Practices

  • Apply data minimization first: send only the fields a given task genuinely requires, before considering redaction of what remains.
  • Redact known PII patterns before constructing the prompt, and keep the real-value mapping in your own code rather than ever exposing it to the model.
  • Use structured output schemas to prevent the model from echoing sensitive values in free-form text, rather than relying solely on prompt instructions.
  • Treat regex-based redaction as one layer of defense, and use a dedicated PII-detection tool for applications with real compliance obligations.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Sensitive Data Handling and get answers drawn from it.

Signed-in readers only.