The Veramask API gives you five ways to anonymize a detected PII entity:
mask, redact, replace, hash, and substitute. They differ in
one question: how much of the original do you keep? Mask preserves the
shape of the value and hides part of it (for example, ****1234).
Redact removes the value entirely. Replace swaps in a fixed
caller-supplied value. Hash turns the value into a deterministic token. With a
consistency_salt, the same input always produces the same output, so a hash acts as a stable handle for the same record across systems. Substitute swaps in realistic synthetic data of the
same type. The right strategy depends on what the downstream system needs: a
human reading the value, a machine that must keep working, or analytics that
must not re-identify the subject. This article walks through each strategy,
the Veramask defaults that ship with it, and a decision matrix for picking
the right one.
The diagram shows the same input — the email jane.doe@example.com — fed
through each of the five strategies. The rest of this article explains why
you would pick one over another, what the Veramask defaults already do, and
how to combine them in a single request.
Why the choice matters
The strategy you pick is the privacy/utility knob for that entity. Strip everything and you lose the diagnostic value of a log line; preserve everything and you have not anonymized anything. The Veramask defaults choose reasonable starting points — last four digits of a credit card, a synthetic name for a person, a salted hash for a crypto address — but a default that is right for one team is rarely right for every team. A production log aggregator, a development seed dataset, and a screenshot fixture for a compliance training deck want three different things from the same email address. The rest of this article is about recognising which case you are in and choosing the strategy to match.
Mask
Mask keeps the structure of the value and hides part of it. In the
Veramask API it takes three configuration fields: masking_char (the
character to insert), chars_to_mask (how many characters to replace), and
from_end (which end to start masking from).
curl -X POST https://api.veramask.com/v1/anonymizeText \
-H "x-api-key: $VERAMASK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": "Alice Smith can be reached at asmith@mygoogle.com",
"overrides": {
"EMAIL_ADDRESS": {
"type": "mask",
"masking_char": "*",
"chars_to_mask": 5,
"from_end": false
}
}
}'
{
"masked_data": "Alice Smith can be reached at *****h@mygoogle.com"
}
The strategy is the Veramask default for CREDIT_CARD (keeps the last
four digits, e.g. ****1234), IP_ADDRESS (keeps the first three
octets, e.g. 192.168.1.0), and MAC_ADDRESS (keeps the OUI, masks the
last three octets, e.g. AD:F3:7A:00:00:00).
Best for: support screens where an agent still needs the last four of a card or a recognisable email shape to find a record; logs that must stay greppable; and any UI that benefits from the user seeing a value is there without showing the whole value.
Operational note: residual plaintext is still PII. Masking the first eight of sixteen card digits still leaves eight digits, which is enough to re-identify the holder against a known-card database. Treat mask as a usability tool, not a privacy boundary.
Redact
Redact is the simplest strategy: the detected value is removed entirely.
The configuration object is just {"type": "redact"}.
curl -X POST https://api.veramask.com/v1/anonymizeText \
-H "x-api-key: $VERAMASK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": "Alice Smith can be reached at asmith@mygoogle.com",
"overrides": {
"EMAIL_ADDRESS": { "type": "redact" }
}
}'
{
"masked_data": "Alice Smith can be reached at "
}
Redact is the Veramask default for URL (the scheme, host, and path
remain, query strings are dropped — this is why ?session=abc123 does not
leak into logs even with no override).
Best for: free-text fields where the PII carries no diagnostic value — error messages, exception payloads, customer-support copy pasted into a ticket. The minimum-friction default when you genuinely do not need the value downstream.
Operational note: redact destroys all signal. If a downstream parser expects the
entity to be a non-empty string, redact will hand it an empty slot and may
crash on whitespace. Combine redact with a wrapper that turns empty values
into a sentinel (<redacted>, ***, null) before shipping the output
onward.
Replace
Replace substitutes every occurrence of the detected entity with one
fixed value that you provide. The configuration is type and new_value.
curl -X POST https://api.veramask.com/v1/anonymizeText \
-H "x-api-key: $VERAMASK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": "Alice Smith can be reached at asmith@mygoogle.com",
"overrides": {
"EMAIL_ADDRESS": {
"type": "replace",
"new_value": "dummy@example.com"
}
}
}'
{
"masked_data": "Alice Smith can be reached at dummy@example.com"
}
Replace is not the default for any entity. The value you supply is global:
every email in the request becomes dummy@example.com, every phone number
becomes +1-555-0100, and so on.
Best for: seed data for screenshots and compliance training decks; demo fixtures that have to look like real data but must not be real; synthetic test inputs where you want a single canonical placeholder rather than a different synthetic value per occurrence.
Operational note: every detected entity collapses to one value. If your dataset
has a hundred email addresses, they all become dummy@example.com — so
uniqueness is destroyed by construction, and any downstream check that
relies on distinctness (duplicate detection, per-user correlation, group-by)
will produce nonsense. Replace is for display, not analysis.
Hash
Hash turns the detected value into a fixed-length token. Veramask uses
SHA-256 and, when a consistency_salt is set in settings, salts the
hash so that the same input always produces the same output.
curl -X POST https://api.veramask.com/v1/anonymizeText \
-H "x-api-key: $VERAMASK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": "User Jane Doe (jane.doe@example.com) failed checkout from 203.0.113.42",
"settings": {
"locale": "en_US",
"confidence_threshold": 0.8,
"consistency_salt": "0123456789abcdef"
},
"overrides": {
"IP_ADDRESS": { "type": "hash" }
}
}'
{
"masked_data": "User Jane Doe (jane.doe@example.com) failed checkout from 9f2c1a..."
}
Hash is the default for CRYPTO (full SHA-256 of the address) and, in
practice, the most common choice for IP_ADDRESS in log sanitisation.
Without a consistency_salt, each request hashes independently and the
same value produces a different token on every call; with a salt, the
output is deterministic across calls and across services.
Best for: cross-record correlation. If you need to trace one user across dozens of log lines without storing the original value, hash with a stable salt gives you the join key. The token is a stable handle for the record — every log line that mentions the same email produces the same token, so you can join records across services without ever seeing the original.
Operational note: hash is a pseudonymisation technique. Pseudonymisation is explicitly recognised under GDPR Article 32(1)(a) as a security measure, and a salted hash is the right tool when you need to correlate records across services while reducing the surface area of the original data. The salt is a new secret that must be governed alongside the data — rotate it like any other credential, restrict who can read it, and never log it next to the hashes. Without a salt, hash is non-deterministic: every call produces a different token for the same input, which is fine for one-shot sanitisation but gives you no join key. If the link to the original is no longer needed, delete the salt and the records can no longer be re-derived.
Substitute
Substitute swaps the detected value for realistic synthetic data of the
same type. Configuration is type and an optional attribute that hints
what flavour of synthetic data to produce (e.g. state for a US location
becomes a state name rather than a city).
curl -X POST https://api.veramask.com/v1/anonymizeText \
-H "x-api-key: $VERAMASK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": "Contact asmith@mygoogle.com for support",
"settings": { "consistency_salt": "0123456789abcdef" },
"overrides": {
"EMAIL_ADDRESS": {
"type": "substitute",
"attribute": "email"
}
}
}'
{
"masked_data": "Contact pespinoza@example.com for support"
}
When a consistency_salt is provided in settings and your subscription
plan supports it, the same input always produces the same synthetic output
across calls — every endpoint, every region, every CI run. With the salt,
substitute becomes a join key: the same jane.doe@example.com will appear
in every record that originally referenced that user, without revealing
anything about the original address. Without the salt, synthetic values
vary by request, so substitute is only useful when you don't need to
re-identify the same record later.
Substitute is the Veramask default for PERSON (a fake name from the same locale), LOCATION (a synthetic address or state), PHONE_NUMBER (a fake number in the same format), and EMAIL_ADDRESS (a fake address with a plausible domain). It is the closest strategy to a "functional but not real" copy of the original.
Best for: analytics pipelines that still need to group by user; dev and staging environments seeded with realistic-looking data; datasets shared with third parties where the receiving system has to keep working but the subjects have to stay anonymous. Substitute preserves the shape of the data, so downstream code, regexes, and validators all behave as they would with real values.
Operational note: synthetic is not original. Substitute does not preserve uniqueness, does not preserve relationships between fields (a substituted name and a substituted email will not belong to the same fake person), and on low-cardinality entity types (countries, currencies, states) the synthetic value can collide with the real one in the same dataset. For correlating records, use hash. For preserving function, use substitute.
Decision matrix — what each strategy does
| Strategy | Output shape | Reversible to original? | Format kept? | Length kept? | Joinable across records? |
|---|---|---|---|---|---|
mask |
Partially redacted original | n/a (partial plaintext remains) | Yes (character class preserved) | Configurable (chars_to_mask, from_end) |
Weak (the unmasked tail varies) |
redact |
Empty (entity removed) | No | n/a | n/a | No |
replace |
One fixed caller value | No | Yes (you choose) | Yes | No — every entity collapses to one value |
hash |
Fixed-length hex (SHA-256, salted if consistency_salt is set) |
Pseudonymisation — the salt is a separate secret you govern | No (hex) | Fixed (64 hex chars for SHA-256) | Yes, with consistency_salt (plan-gated) |
substitute |
Realistic synthetic value of the same type | No (synthetic, not derivable) | Yes (same type and locale) | Approximately | With consistency_salt (plan-gated): same input → same synthetic output. Without: no. |
Decision matrix — pick by use case
| If you need to… | Pick | Why |
|---|---|---|
| Show a human the last four of a card, or a recognisable email shape | mask |
Preserves the part of the value that humans use to recognise the record |
| Remove PII from a free-text log line and never need the value again | redact |
Lowest-friction default; the entity disappears entirely |
| Trace one user across dozens of log lines without storing the original | hash with a consistency_salt |
Deterministic token gives you a join key without keeping the value |
| Seed a dev environment or share a dataset with a third party without re-identification risk | substitute |
Synthetic data keeps the pipeline functional, but the subject is fictional |
| Build a screenshot fixture for a compliance training deck | replace |
One global placeholder is easier to read in a static image than a hash |
| Sanitise web-hook payloads from a payment provider before they reach your APM | mask (card) + redact (everything else) |
Card last-four stays useful for support; everything else is noise |
| Default a new service to "no PII in logs" without bespoke per-entity config | redact for free-text + mask for cards |
Two overrides cover most cases; expand from there as you find needs |
Combining all five in one request
A real integration rarely uses one strategy. The Veramask overrides object
is keyed by entity type, so you can mix strategies per entity in a single
request:
{
"data": "User Jane Doe (jane.doe@example.com) called from 203.0.113.42 about order #4711",
"settings": {
"locale": "en_US",
"confidence_threshold": 0.8,
"consistency_salt": "0123456789abcdef"
},
"overrides": {
"PERSON": { "type": "substitute" },
"EMAIL_ADDRESS": {
"type": "mask",
"masking_char": "*",
"chars_to_mask": 4,
"from_end": false
},
"IP_ADDRESS": { "type": "hash" },
"URL": { "type": "redact" },
"PHONE_NUMBER": {
"type": "replace",
"new_value": "+1-555-0100"
}
}
}
That single call replaces the person with a synthetic name, masks the local part of the email, hashes the IP, redacts the URL, and collapses any phone number to a fixed placeholder. The five strategies compose per-entity because they live in the same per-entity overrides map — the request itself does not pick a global mode.
The endpoint contracts and full schema for the overrides object are in the API specifications docs; the strategy reference and configuration fields are in the transformation strategies docs.
FAQ
How does hash relate to GDPR?
Hash is a pseudonymisation technique — exactly the kind of risk-reduction
measure GDPR encourages for systems that hold personal data. A salted hash
gives you the best of both worlds: the log aggregator no longer holds the
original value (a meaningful improvement over plaintext), and the salt is a
separate secret you can govern independently of the data — rotate it, scope
it, audit access. If your goal is to correlate records across services
while keeping the original out of your logs, a salted hash is the right
tool. If your goal is to break the link to the original entirely
(irreversible anonymisation in the strict GDPR sense), that is a different
problem solved by deletion, aggregation, or noise injection, not by
hashing. Veramask supports both directions: hash with a consistency_salt
for pseudonymisation, or redact / replace / substitute to remove the
original outright.
Can I get the original back from substitute or replace?
No. substitute generates a synthetic value with no relationship to the
original that can be inverted; replace substitutes one fixed value you
supplied. Both are irreversible by design. If you need reversibility, the
API is the wrong tool — you want encryption, not anonymisation, and you
should think carefully about who holds the key and under what lawful basis.
Is mask GDPR-compliant?
It depends on how much of the value you keep. Masking the first eight of sixteen card digits still leaves eight digits, which is enough to re-identify the cardholder against a known-card database. Mask is a usability tool, not a privacy boundary. For a privacy boundary you want redact, replace, substitute, or hash — in roughly that order of strength.
Is consistency_salt available on every plan, and when should I use it?
No. consistency_salt is gated by subscription plan — passing it from a
plan that doesn't include it returns a 403 Subscription Limit error
instead of being silently ignored. Check your current plan on the
Subscription page for confirmation.
Reach for it whenever you need to correlate records by subject without
keeping the original value: tracing one user across log lines by hashing
their EMAIL_ADDRESS, joining PERSON records across two systems by
substituting both sides with the same salt, or building a stable synthetic
dataset you can re-derive across CI runs. Skip it when the data is one-shot
(a single log line you'll never join to anything), when you want every call
to produce a fresh value, or when the same fake value across all your
records would itself be a leak.
Does substitute preserve uniqueness?
No. The synthetic name Jane Doe may be produced for a hundred different
original subjects, and a substituted email may collide with a real email
elsewhere in the dataset. If you need a join key, use hash with a
consistency_salt. If you need realistic shape, use substitute. If you
need both, apply them to different entity types in the same overrides
block.
Can I combine strategies per entity in one request?
Yes — that is the normal mode. The overrides object is keyed by entity
type (PERSON, EMAIL_ADDRESS, IP_ADDRESS, and so on), and each value
is its own strategy object. You can mix mask, redact, replace,
hash, and substitute freely across entity types in the same call. The
one rule is that one entity type gets one strategy per call; if you want
to apply two transformations to the same entity, you run the request
twice or chain a second call.
Connect with us
- Follow @veramaskapi on X
- Try the API at veramask.com
- Read the transformation strategies and API specifications docs
