Protect Sensitive Data
Turn on Protect sensitive data for an agent and every message a person sends it is checked before the AI reads it. Card numbers, bank details, Social Security numbers, and your own account numbers are swapped for a short placeholder such as [card ending 4242, exp 08/29 — saved as PM_1]. The AI works with the placeholder, saved conversations show only the placeholder, and the real value is held for the conversation and never saved. Tools you allow can still use the real value, for example to send it to your payment processor.
What it's for
- Taking payments in a chat, text, or call. The agent can collect a card and pass it to your payment API without the number ever appearing in what the AI reads, in conversation history, or in logs.
- Verifying identity. Callers can give the last digits of an SSN or an account number, and only the masked form is kept.
- Catching what people volunteer. People paste whole cards into chat without being asked. Those are caught too, so an unprompted card number doesn't end up in your history.
- Keeping the agent smooth. The placeholder tells the agent what it already has, so it doesn't ask for the card again.
Navigating here
Open the agent in the builder and find Protect sensitive data in its settings. Turn it on, then choose which kinds of data to catch. The setting is saved with the agent version, so publish the agent for it to apply to live conversations. You can also set it through the Poweruser API and the CLI as sensitiveDataProtection.
It's off unless you turn it on.
Key concepts
What gets caught
Every message a person sends the agent is checked: chat in the builder, an app or a website, text messages, what a caller says on a phone call, and answers to private arguments.
| Kind | What's caught |
|---|---|
| Payment cards | Card numbers of 13 to 19 digits, with or without spaces, dashes, or dots, and only when the number is a valid card number. Spoken digits count too, such as "four two four two, double four...". An expiry date (08/29, 08/2029, "August 2029") and a security code are caught when they come with the card, right after a request for them, or next to words like "security code" or "CVV". |
| Bank accounts | A routing number with a valid check digit, and an account number next to words like "account", "checking", or "savings". |
| Social Security numbers | 123-45-6789, or nine digits next to words like "Social Security" or "SSN". |
| Your account numbers | Account numbers in your own format, such as a utility or policy number. Set accountNumberPattern to a regular expression that matches them; without a pattern, nothing is caught as an account number. |
A message that dumps everything at once, such as a card number, expiry, security code, and ZIP code in one line, becomes one saved card. If the person sends the security code or expiry in a later message, it's added to the card they already gave.
A ZIP code given with a card is saved with it, so a tool can send it for address checks, but it stays visible in the text because it isn't sensitive on its own.
Turn on modelPass to also check long, messy messages with a private AI model that runs inside Autessa. It helps with values that are split up or written oddly. It only runs on long messages that contain a lot of digits.
What the AI sees
The value is replaced with a placeholder before the AI reads the message:
| Captured | Placeholder |
|---|---|
| Card | [card ending 4242, exp 08/29 — saved as PM_1] (the expiry part appears only when an expiry was given) |
| Security code only, added to the latest card | [security code saved to PM_1] |
| Bank account | [bank account ending 6789 (routing ending 0021) — saved as BA_1] |
| SSN | [SSN ending 1234 — saved as SSN_1] |
| Your account number | [account ending 5678 — saved as AC_1] |
So if a customer types "my card is 4242 4242 4242 4242 exp 08/29 cvc 123", the agent reads "my card is [card ending 4242, exp 08/29 — saved as PM_1] [security code saved to PM_1]".
The name after "saved as" is the item's handle: PM_ for payment cards, BA_ for bank accounts, SSN_ for Social Security numbers, and AC_ for account numbers, numbered from 1 in each conversation. The agent passes the handle to tools, never the value.
Held for the conversation, never saved
The real values are kept encrypted, only for the conversation they were given in:
- They're deleted when the conversation closes, or after
ttlMinutes(240 minutes by default), whichever comes first. - They're never written to your AutessaDB tables, files, conversation history, or logs.
- Everything that is saved shows only the placeholder: the agent's conversation history, Conversation Sync rows, text message records, call transcripts, and execution logs. In the conversation viewer, placeholders show as chips.
What is kept is a record that the item existed (its handle, its kind, and the masked text such as "card ending 4242") and a record of each time a tool used it: which tool, which host, when, and whether it worked.
How the agent avoids asking again
Because the placeholder says what was saved, the agent can see it already has the card, the expiry, and the security code. If a piece is missing, for example the placeholder has no expiry, the agent asks only for that piece, and the answer is added to the same handle. You can make this explicit in the agent's instructions: "When the customer has given a card, use its PM handle. Don't ask for the card again."
How tools use the values
A tool gets nothing but the handle unless you give it vault access. Turn it on in the tool's settings, or set vaultAccess on the tool:
{
"vaultAccess": {
"enabled": true,
"allowedHosts": ["api.payments.example.com"]
}
}
enabled: whether the tool may use saved values at all.allowedHosts: the exact hostnames the values may be sent to.
In a script tool
A script tool uses the vault helper. It works on the conversation the tool is running in.
| Call | Returns |
|---|---|
vault.list() | The items saved in this conversation, masked: [{ handle, type, display, fields, source, createdAt, brand }]. display is the masked text, such as card ending 4242, exp 08/29. fields lists which pieces were given, by the names below. source is where the value came from: CHAT, SMS, VOICE, KEYPAD, or PRIVATE_ARG. brand is the card network for a payment card (for example VISA), when known. |
vault.has(handle, field) | true if that piece of the item was given. |
vault.ref(handle, field[, { format }]) | An opaque reference, [[autessa-vault:…]], to put in an outgoing request. For expiry, format picks the shape the value is filled in as: MM/YY (default), MM/YYYY, MMYY, MMYYYY, MM, YY, YYYY, or YYYY-MM. Use MM and YYYY separately for APIs that want the month and year as two fields. |
vault.forget(handle) | Deletes the item right away, for example after a payment goes through. Returns true if there was an item to delete. |
The field names are:
| Item | Fields |
|---|---|
| Payment card | number, expiry, cvv, postalCode, name |
| Bank account | routing, account, accountType, holderName |
| SSN | ssn |
| Account number | accountNumber |
vault.list() and vault.has() work in any script tool. vault.ref() throws VAULT_ACCESS_DENIED unless the tool has vault access turned on.
When the value is filled in
A reference from vault.ref works the way a secret's handle does. The API helper (api.get, api.post, api.put, api.patch, api.delete) swaps it for the real value only when both are true:
- the endpoint uses
https://, and - the endpoint's hostname exactly matches one of the tool's
allowedHosts.
Otherwise the request isn't sent and the call returns an error. Anywhere else, such as console.log, response.setResponse, AutessaDB writes, or agent prompts, the reference stays as its text.
Before the response reaches your script, any saved value that comes back in the response body, headers, or an error is replaced, so a service that echoes the card number can't leak it into your script or the agent. Each use is recorded.
When a value has expired
If the conversation closed or ttlMinutes passed, the value is gone and vault.ref throws VAULT_VALUE_EXPIRED. Catch it and tell the agent to ask again:
const handle = variables.get("paymentMethod");
let number = null;
try {
number = vault.ref(handle, "number");
} catch (e) {
if (!String(e).includes("VAULT_VALUE_EXPIRED")) throw e;
}
if (number === null) {
response.setResponse(JSON.stringify({
ok: false,
error: "VAULT_VALUE_EXPIRED",
message: "The card details have expired. Ask the customer for the card again."
}));
} else {
// ... use number in an api.* request
}
Private arguments that save to the vault
A private argument collects a value straight from the person, without the AI. Set vaultField on it and the answer is saved like any other caught value, and the tool receives the handle (for example PM_1) in that argument instead of the value:
vaultField: which piece the answer is, such asCARD_NUMBER,CARD_EXPIRY,CARD_CVV,POSTAL_CODE,CARDHOLDER_NAME,BANK_ROUTING,BANK_ACCOUNT,BANK_ACCOUNT_TYPE,ACCOUNT_HOLDER_NAME,SSN, orACCOUNT_NUMBER.vaultGroup: which kind of item it belongs to:PAYMENT_CARD,BANK_ACCOUNT,SSN, orACCOUNT. A tool's private arguments with the samevaultGroupare saved together as one item, so a card number, expiry, and security code asked for together become onePM_handle.
In chat, a private argument with a vaultField is asked for with a masked input, like a password field.
On a phone call, use the Secure Keypad Capture tool instead: the caller types the card on their keypad, the recording is paused, and the AI never hears the digits.
How to take a payment
This example takes a card payment in a chat, text, or call and sends it to a payment API.
- On the agent, turn on Protect sensitive data with Payment cards selected.
- Create a script tool called
ChargeCardwith two arguments,paymentMethod(the card's handle, such asPM_1) andamount, and turn on vault access for your payment API's host. - Add the tool to the agent and tell it when to use it.
{
"name": "ChargeCard",
"description": "Charges the customer's saved card. Pass the card's handle, such as PM_1, and the amount in dollars.",
"toolConfig": {
"toolType": "AUTESSA_SCRIPT",
"config": {
"supportedLanguage": "AUTESSASCRIPT",
"script": "..."
}
},
"arguments": {
"paymentMethod": {
"description": "Handle of the saved card, such as PM_1",
"isRequired": true,
"argumentSchema": { "type": "string" }
},
"amount": {
"description": "Amount to charge, in dollars",
"isRequired": true,
"argumentSchema": { "type": "number" }
}
},
"vaultAccess": {
"enabled": true,
"allowedHosts": ["api.payments.example.com"]
}
}
The script:
function charge() {
const handle = variables.get("paymentMethod");
const amount = parseFloat(variables.get("amount"));
for (const field of ["number", "expiry", "cvv"]) {
if (!vault.has(handle, field)) {
return { ok: false, missing: field };
}
}
let card;
try {
card = {
number: vault.ref(handle, "number"),
expiry: vault.ref(handle, "expiry"),
cvv: vault.ref(handle, "cvv"),
postalCode: vault.has(handle, "postalCode") ? vault.ref(handle, "postalCode") : null
};
} catch (e) {
if (String(e).includes("VAULT_VALUE_EXPIRED")) {
return { ok: false, error: "VAULT_VALUE_EXPIRED", message: "Ask the customer for the card again." };
}
throw e;
}
const res = JSON.parse(api.post(JSON.stringify({
endpoint: "https://api.payments.example.com/v1/charges",
headers: { "Authorization": "Bearer " + secrets.ref("payments-api-key") },
body: { amount: Math.round(amount * 100), card: card }
})));
if (res.error || res.statusCode >= 300) {
return { ok: false, status: res.statusCode, body: res.body };
}
vault.forget(handle);
return { ok: true, charge: JSON.parse(res.body) };
}
response.setResponse(JSON.stringify(charge()));
If the payment service echoes any part of the card back in res.body, it's already been replaced before the script sees it, so returning the body to the agent is safe.
Agent instructions:
When the customer wants to pay, ask for their card number, expiry, and security code. Once you see a card placeholder like "saved as PM_1" with an expiry and a security code, call ChargeCard with that handle and the amount. Don't ask for the card again. If ChargeCard says a field is missing, ask only for that field. If it says the card details expired, ask for the card again.
The agent only ever sees PM_1. The card number is filled in only inside the request to api.payments.example.com, the API key comes from a secret, and the conversation history shows only the placeholder.
Taking card numbers in a conversation and sending them to a payment processor from your tools puts that flow in scope for PCI DSS, with its own requirements and assessment. Protect sensitive data keeps the values encrypted, short-lived, and out of the AI and your records, but it doesn't remove that scope. If you'd rather stay out of scope, many processors offer hosted payment pages, payment links you can text or email to the customer, or their own phone payment line you can transfer the caller to.
Settings
sensitiveDataProtection on the agent:
| Field | What it does |
|---|---|
enabled | Turns protection on. Defaults to false. |
detectors | Which kinds to catch: PAYMENT_CARD, BANK_ACCOUNT, SSN, ACCOUNT. Defaults to cards, bank accounts, and SSNs. |
accountNumberPattern | A regular expression matching your own account numbers. Needed for ACCOUNT. |
modelPass | Also check long, messy messages with a private AI model. Defaults to false. |
ttlMinutes | The longest a value is kept, even if the conversation stays open. Defaults to 240. |
When you update an agent and leave sensitiveDataProtection out, the current setting is kept. To turn protection off, send { "enabled": false }.
{
"sensitiveDataProtection": {
"enabled": true,
"detectors": ["PAYMENT_CARD", "BANK_ACCOUNT", "ACCOUNT"],
"accountNumberPattern": "\\b\\d{10}\\b",
"modelPass": false,
"ttlMinutes": 60
}
}
Going further
Protect sensitive data keeps values out of the AI. To control who can get sensitive information in the first place, use the AI Application Firewall: it can ask the person to verify before a tool runs, and cap how many sensitive records one person can get per conversation and per day. You decide which tools and tables hold sensitive records.
Limits
- Protection is set per agent. Conversations run by a manager, and the agents a manager calls, aren't scanned yet. Point the phone number, text line, or chat straight at the agent that has protection on.
- Answers to ordinary private arguments (ones without a
vaultField, such as an API key) show as[private value provided]in the conversation history.