Gateway
Veilio Gateway sits between an application and its database. The screen keeps showing real values. The database stores tokens. Veilio stays the vault: the gateway calls tokenize and detokenize, and it does not keep a copy of the plaintext.
Use it when the application should keep working as it does today, and the database should no longer hold identity fields in clear.
The Gateway is an integration layer (library or SQL proxy), not a separate paid product. You bring a Veilio API key (SaaS or on-premise), a database you own, and a policy file.
Current image: veilio/gateway:0.8.0.
What each side sees
| Place | Content |
|---|---|
| Application screen | Real value |
| Application database | Token tok_ plus 32 characters |
| Veilio vault | Real value, encrypted with the account key |
| Gateway logs and metrics | Field type and counters. Never the value, never the full token |
The gateway does not write the customer database by itself in library mode. Your code writes the token it returns. In SQL mode, the gateway writes the token to the database.
Pick an integration
| Situation | What to install |
|---|---|
| You own the backend and want the screens unchanged | Library. Call protect before a write and reveal before display. |
| You own the backend and only a few flows need protection | SDK or HTTP. Not the gateway. |
| Vendor software, database on your side, connection string you can change | SQL gateway. The application points at the gateway. The gateway points at the database. |
| Vendor hosts both the screen and the database | No gateway. Ask the vendor to call Veilio, or protect the copy that lands on your server. |
One gateway process uses one API key and one organization. Tokens created with that key cannot be read with another key. Two keys of the same organization share the same token cap, request quota, and credits. The limit of 1,000 calls per minute is on the key. The gateway sends at most 1,000 values per call (Core bulk ceiling), four calls at a time, and waits 30 seconds per call. Set VEILIO_TIMEOUT_MS to change the wait.
Policy file (required)
The policy tells the gateway which table columns to tokenize. It is a JSON file on the host, mounted into the container. It is not downloaded from the Veilio dashboard: you create it once per deployment (or start from the example below).
Then adapt model / field / type / action to your schema, and save it next to where you run Docker (for example as clients.policy.json).
Identity fields belong in it: email, phone, name, IBAN. Leave out amounts, quantities, status, account numbers, document numbers, and any column the application sorts, totals, or uses as a key. A column absent from the policy is left unchanged.
[
{ "model": "clients", "field": "email", "type": "email", "action": "tokenize" },
{ "model": "clients", "field": "phone", "type": "phone", "action": "tokenize" },
{ "model": "clients", "field": "name", "type": "name", "action": "ignore" }
]| Field | Required | Description |
|---|---|---|
model | Yes | Table or model name |
field | Yes | Column name |
type | Recommended | Veilio type: email, phone, name, iban, or a custom label |
action | Yes | tokenize or ignore |
Protected columns must be wide text. A token does not fit in a varchar sized for a phone number or a short email. Widen those columns before the first write.
Empty values stay empty. They are not tokenized.
Install the image
The public install is one Docker image, veilio/gateway. You do not need the source, and you do not pull a different image per database.
You still bring three things: an API key from Dashboard → API Keys, a database you own, and a policy file. The key alone is not enough. The gateway has to know which database to open and which columns to protect.
GATEWAY_PROTOCOL chooses the engine. If you omit it, the gateway speaks Postgres. The process then opens the standard port of that engine. You do not set the port yourself.
GATEWAY_PROTOCOL | Port the process opens | Database URL |
|---|---|---|
postgres | 5433 | postgres://USER:PASSWORD@HOST:5432/app |
mysql | 3306 | mysql://USER:PASSWORD@HOST:3306/app. MariaDB uses this value. |
mssql | 1433 | mssql://USER:PASSWORD@HOST:1433/app |
mongo | 27017 | mongodb://USER:PASSWORD@HOST:27017/app |
oracle | 1521 | The process accepts the connection and refuses the statement. Nothing is written. |
docker run -p is separate. It publishes the container port onto your machine. Use the same number as the table. Setting GATEWAY_PROTOCOL does not publish the port for you.
Protected columns must already be wide text. A token does not fit in a short varchar.
Mount your policy file (the example from Policy file above, after you adapted it). email and phone are tokenized. name stays as typed when its action is ignore.
If the database runs on the same machine as Docker, its host name inside the container is host.docker.internal, not localhost.
Postgres, the default:
export VEILIO_API_KEY="01234567890123456789012345678901"
export VEILIO_BASE_URL="https://app.veilio.fr/api"
export GATEWAY_UPSTREAM="postgres://USER:PASSWORD@host.docker.internal:5432/app"
docker pull veilio/gateway:0.8.0
docker run --rm -p 5433:5433 -p 8080:8080 \
--add-host=host.docker.internal:host-gateway \
-e VEILIO_API_KEY \
-e VEILIO_BASE_URL \
-e GATEWAY_UPSTREAM \
-v "$PWD/clients.policy.json:/policy/policy.json:ro" \
veilio/gateway:0.8.0MariaDB or MySQL. The process opens 3306 because of GATEWAY_PROTOCOL.
export GATEWAY_PROTOCOL=mysql
export GATEWAY_UPSTREAM="mysql://USER:PASSWORD@host.docker.internal:3306/app"
docker run --rm -p 3306:3306 -p 8080:8080 \
--add-host=host.docker.internal:host-gateway \
-e VEILIO_API_KEY \
-e VEILIO_BASE_URL \
-e GATEWAY_PROTOCOL \
-e GATEWAY_UPSTREAM \
-v "$PWD/clients.policy.json:/policy/policy.json:ro" \
veilio/gateway:0.8.0VEILIO_API_KEY is your key. VEILIO_BASE_URL stays https://app.veilio.fr/api for Veilio SaaS. GATEWAY_UPSTREAM is the real database. On-premise Veilio: replace the base URL with https://<your-domain>/api.
Check that the process, the policy, and the database answer:
curl -s http://127.0.0.1:8080/healthA healthy gateway returns {"ok":true,"policy":true,"database":true}. This call does not reveal any value.
Point the application at 127.0.0.1 and the port from the table. Any user name is accepted. Do not put the real database password in the application. The gateway already holds it.
psql "host=127.0.0.1 port=5433 dbname=app user=app" -c \
"INSERT INTO clients (id, email, phone, name) VALUES ('1', 'marie.durand@example.com', '+33612345678', 'Marie Durand');"Read the real database on its own port, not through the gateway. email and phone show FPT masks (not the real values). name is still Marie Durand. The same SELECT through the gateway shows the original email and phone.
Library
Use this when your own backend can call the gateway before it writes. The API key stays in the server environment. The Docker image above is the install that does not require this package.
export VEILIO_API_KEY="01234567890123456789012345678901"
export VEILIO_BASE_URL="https://app.veilio.fr/api"On-premise: set VEILIO_BASE_URL to https://<your-domain>/api.
import { VeilioGateway, loadPolicy } from "@veilio/gateway";
const gateway = new VeilioGateway({
policy: await loadPolicy("./clients.policy.json"),
apiKey: process.env.VEILIO_API_KEY,
baseUrl: process.env.VEILIO_BASE_URL,
});
// Insert. `stored` is omitted: there is no token yet.
const row = await gateway.protect("clients", {
id: "1",
email: "marie.durand@example.com",
phone: "+33612345678",
name: "Marie Durand",
});
await db.insert("clients", row);
// Read. The screen receives the original email and phone.
const stored = await db.findById("clients", "1");
const screen = await gateway.reveal("clients", stored);
// Update. Pass the stored row so an unchanged value keeps the same token.
const next = await gateway.protect("clients", submitted, stored);
await db.update("clients", "1", next);protect returns a copy safe to persist. Protected fields are tokens. Other fields are unchanged. If Veilio does not answer, protect throws and you must not write the plaintext.
reveal returns a copy for the screen. If a token cannot be opened, the read fails. show-token is a diagnostic setting. Do not use it in production.
Lists use protectMany and revealMany. One Veilio call covers the whole list.
An equality filter on a protected field uses match. It returns the tokens for that exact value. Query the database with those tokens, then reveal the rows. An unknown value is an empty list. Sorting or LIKE on the original value is not supported.
SQL gateway
The image in Install the image is this mode. Use it when you cannot change the application code, you own the database, and you can point the application at another host.
The application changes only its connection string. The gateway holds the real database URL and the API key.
The same write, as the application would send it:
INSERT INTO clients (id, email, phone, name)
VALUES ('1', 'marie.durand@example.com', '+33612345678', 'Marie Durand');After the gateway, the database contains two FPT masks and the name in clear. A later SELECT through the gateway returns the original email and phone to the application.
Accepted SQL on a protected table:
INSERTUPDATEwith aWHEREofcolumn = valueconditionsSELECTof columns or*, with optional equality filters,ORDER BYon a non-protected column,LIMIT, andOFFSET
A statement that does not touch a protected table is passed through unchanged.
Refused on a protected table, with nothing written: LIKE, a sort on a protected column, JOIN, COPY, functions, and any SQL the gateway cannot rewrite. An editor upgrade can emit SQL the gateway does not recognize. That statement is refused. It is not guessed.
WHERE email = 'marie.durand@example.com' becomes a list of tokens. If none exist, the query returns no row, and the plaintext is not sent to the database.
Postgres, MySQL, MariaDB, and SQL Server share that subset. MongoDB accepts insert, find, and update with equality on top-level fields. Oracle accepts the connection and refuses the statement.
Saving a screen without changes
Opening a record shows the real values. Saving that screen again must not create a new token for every field.
The gateway reads the current token, asks Veilio for the stored value, and compares it in memory. An equal value is written back as the same token. Only a changed value is tokenized. The comparison is not written to the logs.
Quota
Each new protected value is one token and one request unit. A bulk call is one HTTP request and N units. A read is one request unit per token. A save that does not change the value still spends the read used for the comparison, and creates no new token.
Under the included monthly quota, the call counts against the quota and the credit balance does not move. Above it, the call proceeds only when enough credits remain, and those credits are debited. The token cap is a lifetime total for the organization, not a monthly reset.
Lookup (WHERE on a protected column) counts as one request when it is made with the API key.
Run it
GET /health reports whether the process is up, the policy is loaded, and the database answers. That call does not detokenize. If Veilio is down, protected writes and reads fail, and health stays green while the process and the database answer.
GET /metrics counts writes, reads, lookups, and failures, by field type.
Default read mode is fail. Set VEILIO_READ_FAILURE=show-token only to diagnose a token that cannot be opened.
Before a wider rollout:
- Write the policy for one table and two or three columns.
- Create a record (include
idfor FPT). In SQL, protected columns hold masks (ortok_…ifmodeisstandard) and the others are unchanged. - Open the record. The screen shows what was typed.
- Save without changes. The tokens in the database stay the same.
- Change one field. Only that token changes.
- Cut access to Veilio and try a write. The plaintext is not stored.
- Read the gateway logs. The trial email is not there.
Data already in the database is not tokenized by the gateway. Migrating history is a separate pass: copy the table, tokenize, write the tokens, and check that a read returns the same values. Keep a restore path for the pilot columns before you run that pass on the real database.