# Developer Platform

Welcome to your team’s developer platform

<h2 align="center">Veilio Developer Platform</h2>

<p align="center"><em>Make stolen data worthless</em></p>

<p align="center"></p>

#### Secure sensitive data. Accelerate compliance. Keep product velocity.

Veilio helps companies protect personal data by replacing raw values (email, phone, address, IDs) with secure tokens.\
Your teams keep building fast, while reducing exposure risk and strengthening compliance posture.

Start in 5 minutes · Explore Use Cases · Read API Reference

***

### Why teams choose Veilio

* **Compliance-first by design**: supports privacy-by-design initiatives and helps reduce risk around personal data handling
* **Lower breach impact**: tokenized databases are far less exploitable if leaked
* **Fast time-to-value**: first integration in hours via SDK or HTTP
* **Business-friendly rollout**: protects data without disrupting core user journeys

***

### How Veilio works

1. Your app sends sensitive data to Veilio
2. Veilio returns a token (`tok_...`)
3. Your app stores the token, not the raw value
4. Detokenization is performed only when business context requires it

This model improves governance, limits unnecessary data exposure, and supports auditable access patterns.

***

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><h4>🚀</h4></td><td><h4> Quickstart</h4></td><td>Run your first tokenize and detokenize flow in minutes.</td><td><a href="/documentation/getting-started/quickstart">Quickstart</a></td><td><a href="https://1085286164-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXrHbkdBCg9coYlnvtmTu%2Fuploads%2FDVHK0HlkDKCOQ8Imoehj%2Fwebsite-html-code-browser-view-printed-white-paper-closeup-view.jpg?alt=media&amp;token=ea5224bd-7d95-48d2-9b7d-924308091456">website-html-code-browser-view-printed-white-paper-closeup-view.jpg</a></td></tr><tr><td><h4>🧩</h4></td><td><h4>Use cases</h4></td><td>Learn more about Veilio use cases.</td><td><a href="/documentation/basics/use-cases">Veilio Use Cases</a></td><td><a href="https://1085286164-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXrHbkdBCg9coYlnvtmTu%2Fuploads%2FgrsVyAYXZQH3BZPXjthu%2F9603026.jpg?alt=media&amp;token=5aa2060b-7cac-4287-8d61-19e53ff7d4df">9603026.jpg</a></td></tr><tr><td>📘</td><td><strong>API reference</strong></td><td>Browse, test, and implement APIs.</td><td><a href="https://docs.veilio.xyz/api-mcp/">API Reference &amp; MCP </a></td><td><a href="https://1085286164-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXrHbkdBCg9coYlnvtmTu%2Fuploads%2FZWxOLEg2Vqi0y76uxzLh%2F7015995.jpg?alt=media&amp;token=012f83df-9665-4678-8070-3316920739a1">7015995.jpg</a></td></tr></tbody></table>

### Typical first wins

* Protect profile/account data without changing UX,
* Tokenize contact and lead forms at collection time,
* Secure legacy CSV datasets through batch processing

Browse simple use cases

***

### Security and go-live

Prepare your production rollout with practical guidance:

* Security Best Practices
* Troubleshooting
* Go-live Checklist

### Need help with your first POC?&#x20;

[Contact us](https://veilio.xyz/contact)

<h3 align="center">Start your journey </h3>

<p align="center"><a href="https://veilio.xyz/signup" class="button primary">Sign up</a> <a href="https://veilio.xyz/login" class="button secondary">Log in</a></p>

<h2 align="center">Keep in touch with us !</h2>

<p align="center">Visit our blog and follow us on social media to stay up to date with all our latest updates.</p>

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><h4><i class="fa-lightbulb">:lightbulb:</i></h4></td><td><strong>Veilio Blog</strong></td><td>We strive to deliver valuable insights, thoughtful feedback, and expert analysis on cybersecurity topics.</td><td><a href="https://veilio.xyz/blog" class="button secondary">Have a look!</a></td><td></td></tr><tr><td><h4><i class="fa-linkedin-in">:linkedin-in:</i></h4></td><td><strong>Follow us on socials!</strong></td><td>Follow us for the latest updates, every bit of visibility helps us grow Veilio.</td><td><a href="https://www.linkedin.com/company/veilio/" class="button secondary">LinkedIn</a></td><td></td></tr></tbody></table>


# Welcome to Veilio !

<figure><img src="https://871091000-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0WqIAiGHfaanrggvU6xD%2Fuploads%2FEDp7V0504BpAcLkiF6PJ%2Fimage.png?alt=media&amp;token=edbb41b4-a1e7-4c88-9373-5ea5d16fc975" alt=""><figcaption></figcaption></figure>

Veilio helps you protect sensitive customer data by replacing raw values (email, phone, IDs) with secure tokens.

Your application stores tokens. When needed, your backend can retrieve original values with controlled detokenization calls.

***

#### Why teams integrate Veilio

* Reduce sensitive data exposure in application databases
* Keep existing product workflows with minimal code changes
* Improve auditability and compliance readiness (GDPR, crypto-shredding, SIEM)

***

#### What you will build in this guide

1. Generate and configure an API key
2. Tokenize a sensitive value
3. Store and use the returned token
4. Detokenize only when needed
5. Ship to production with security best practices

***

#### Integration paths

| Path                 | Best for                               |
| -------------------- | -------------------------------------- |
| **Quickstart**       | First working flow in 5 minutes (curl) |
| **SDK Javascript**   | Node.js / TypeScript backends          |
| **SDK Python**       | Python backends                        |
| **HTTP Integration** | Any language, full control             |
| **API Reference**    | Complete endpoint list                 |

***

#### Jump right in

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><i class="fa-bolt">:bolt:</i></h4></td><td><strong>Quickstart</strong></td><td>Implement Veilio in minutes</td><td></td><td></td><td><a href="/documentation/getting-started/quickstart">Quickstart</a></td></tr><tr><td><h4><i class="fa-leaf">:leaf:</i></h4></td><td><strong>Veilio</strong></td><td>Learn more about Veilio</td><td></td><td></td><td><a href="/documentation/basics/use-cases">Veilio Use Cases</a></td></tr><tr><td><h4><i class="fa-globe-pointer">:globe-pointer:</i></h4></td><td><strong>Suggestion</strong></td><td>Any idea is valuable</td><td></td><td></td><td><a href="/documentation/help/suggestions">Suggestions &amp; Contact</a></td></tr></tbody></table>

***

#### Advanced topics

| Topic                      | Page              |
| -------------------------- | ----------------- |
| Historical CRM import      | Migration Mode    |
| Account exit / portability | Offboarding       |
| Dataset upload & sharing   | Datasets Upload   |
| Retention & shredding      | Data Lifecycle    |
| Go-live checklist          | Go-live Checklist |

***

#### Related links

* Product website: <https://veilio.xyz>
* Documentation: <https://docs.veilio.xyz>
* Contact / Demo: <https://veilio.xyz/contact>


# Quickstart

This quickstart gives you a first end-to-end Veilio flow.

<figure><img src="https://871091000-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0WqIAiGHfaanrggvU6xD%2Fuploads%2Fe4BScrVBANmNHxON00Ot%2Fimage%20(2).png?alt=media&amp;token=0562d36a-cada-459c-8325-414bbb40a6ba" alt=""><figcaption></figcaption></figure>

#### Prerequisites

* A Veilio account and API key from **Dashboard → API Keys**
* A backend environment (never call Veilio with secrets from frontend or mobile code)
* `curl` installed (or use the JavaScript / Python SDK)

#### 1) Create an API key

1. Sign in at [app.veilio.xyz](https://app.veilio.xyz).
2. Open **Dashboard → API Keys**.
3. Click **Create API key**, choose **Standard** purpose for day-to-day operations.
4. Copy the key once — it is shown only at creation time.

#### 2) Set environment variables

```bash
export VEILIO_API_KEY="veil_live_xxxxxxxx"
export VEILIO_BASE_URL="https://app.veilio.xyz/api"
```

For **on-premise** deployments, set `VEILIO_BASE_URL` to your instance URL, e.g. `https://veilio.your-company.com/api`.

#### 3) Tokenize a sensitive value

```bash
curl -s -X POST "$VEILIO_BASE_URL/tokenize" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"data":"support@veilio.xyz","type":"email"}'
```

Expected response:

```json
{
  "token": "tok_abc123...",
  "createdAt": "2026-03-26T10:00:00.000Z"
}
```

**Store only `token` in your database** — never the raw email.

#### 4) Detokenize when required

```bash
curl -s -X POST "$VEILIO_BASE_URL/detokenize" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"token":"tok_abc123...","reason":"Send user notification"}'
```

Expected response:

```json
{
  "data": "support@veilio.xyz",
  "accessedAt": "2026-03-26T10:01:00.000Z"
}
```

The `reason` field is logged for audit and compliance.

#### 5) Recommended application pattern

| Path                                     | Pattern                                                  |
| ---------------------------------------- | -------------------------------------------------------- |
| **Write** (create / update user)         | Tokenize before insert or update                         |
| **Read** (display profile)               | Store and display the token, or a masked placeholder     |
| **Action** (send email, export, support) | Detokenize only at the business step that needs raw data |
| **Audit**                                | Always pass a clear `reason` on detokenization           |

{% hint style="warning" %}
**Do not** detokenize automatically on every page load to show raw PII in the browser. That exposes sensitive data in your frontend, logs, and network traces. Detokenize on the **server**, only when a specific workflow requires it (email send, PDF export, support ticket, etc.).
{% endhint %}

#### Next steps

| Goal                          | Page                                        |
| ----------------------------- | ------------------------------------------- |
| SDK integration (recommended) | SDK Javascript · SDK Python                 |
| Direct HTTP / all endpoints   | HTTP Integration · API Reference            |
| Production checklist          | Security Best Practices · Go-live Checklist |
| Historical import             | Historical Migration Mode                   |

**Related links**

* Product website: <https://veilio.xyz>
* Contact : <https://veilio.xyz/contact>


# SDK Javascript Integration

Use an SDK for the fastest production integration.

#### Node.js / TypeScript

Install:

```bash
npm install @veilio/sdk
```

Example:

```ts
import { VeilioClient } from "@veilio/sdk";

const veilio = new VeilioClient({
  apiKey: process.env.VEILIO_API_KEY!,
  baseUrl: process.env.VEILIO_BASE_URL || "https://app.veilio.xyz/api",
});

export async function createCustomer(email: string) {
  const { token } = await veilio.tokenize({
    data: email,
    type: "email",
  });

  // Store token in your DB, not the raw email.
  return { emailToken: token };
}

export async function sendWelcomeEmail(emailToken: string) {
  const { data: email } = await veilio.detokenize({
    token: emailToken,
    reason: "Send welcome email",
  });

  return email;
}

// Call when a user requests account deletion (GDPR right to erasure)
export async function deleteUserData(emailToken: string) {
  await veilio.shredToken({
    token: emailToken,
    reason: "GDPR - user deletion request",
  });
}
```

#### Client configuration

```ts
const veilio = new VeilioClient({
  apiKey: string,       // Required — your Veilio API key
  baseUrl?: string,     // Default: https://app.veilio.xyz/api
  timeout?: number,     // Request timeout in ms (default: 30000)
  maxRetries?: number,  // Retries on rate limits / network errors (default: 3)
});
```

For **on-premise** deployments, point `baseUrl` to your instance:

```ts
const veilio = new VeilioClient({
  apiKey: process.env.VEILIO_API_KEY!,
  baseUrl: "https://your-domain.com/api",
});
```

Environment variables:

| Variable          | Description                               |
| ----------------- | ----------------------------------------- |
| `VEILIO_API_KEY`  | API key from the Veilio dashboard         |
| `VEILIO_BASE_URL` | API base URL (optional, defaults to SaaS) |

#### API reference

**`tokenize(options)`**

Tokenize a single field.

```ts
const result = await veilio.tokenize({
  data: "john@example.com",
  type: "email",                    // Optional: email, phone, ssn, etc.
  metadata: { source: "signup" },   // Optional
  retention: {                      // Optional — automatic shredding
    ttlDays: 30,                    // Shred after N days
    // or retentionUntil: "2026-12-31T23:59:59.000Z"
  },
});

// Returns: { token, createdAt, retentionUntil? }
```

**`tokenizeBulk(options)`**

Tokenize multiple fields in one request.

```ts
const result = await veilio.tokenizeBulk({
  fields: [
    { data: "john@example.com", type: "email" },
    { data: "+33612345678", type: "phone", retention: { ttlDays: 365 } },
  ],
});

// Returns: { tokens, summary: { total, success, failed }, errors?, createdAt }
```

**`detokenize(options)`**

```ts
const result = await veilio.detokenize({
  token: "tok_abc123...",
  reason: "Send email",  // Optional — logged for audit
});

// Returns: { data, accessedAt }
```

**`detokenizeBulk(options)`**

```ts
const result = await veilio.detokenizeBulk({
  tokens: [
    { token: "tok_abc123...", reason: "Profile view" },
    { token: "tok_def456...", reason: "Profile view" },
  ],
  // Optional: one audit log line for the whole batch
  reason: "Profile view",
});

// Returns: { results, summary, errors?, accessedAt }
```

**`shredToken(options)`**

Immediately and irreversibly destroy a token (cryptographic erasure).

```ts
const result = await veilio.shredToken({
  token: "tok_abc123...",
  reason: "GDPR erase request",  // Optional
});

// Returns: { token, shreddedAt }
```

**`tokenizeFormat(options)`**

Tokenize structured data (JSON, CSV, or SQL).

```ts
const result = await veilio.tokenizeFormat({
  data: JSON.stringify({ email: "john@example.com", phone: "+33612345678" }),
  format: "json",  // Optional — auto-detected if omitted ("json" | "csv" | "sql")
  options: {
    fields: ["email"],              // Optional — tokenize only specific paths
    retention: { ttlDays: 365 },    // Optional
    csv: {                          // CSV-specific options
      delimiter: ",",
      hasHeaders: true,
    },
  },
});

// Returns: { format, tokenizedData, tokens: [{ path, token, type? }], summary, metadata? }
```

**`detokenizeFormat(options)`**

Restore original values in structured data.

```ts
const result = await veilio.detokenizeFormat({
  tokenizedData: tokenizedJson,
  format: "json",
  tokens: [
    { path: "email", token: "tok_abc123..." },
  ],
});

// Returns: { format, detokenizedData, summary: { tokensProcessed } }
```

#### Bulk operations

For imports, migrations, or high-throughput jobs:

* `tokenizeBulk`
* `detokenizeBulk`

Use batching and retry logic for `429` responses. The SDK retries automatically (respecting `Retry-After` headers) up to `maxRetries` times.

```ts
const bulk = await veilio.tokenizeBulk({
  fields: [
    { data: "alice@example.com", type: "email" },
    { data: "+33612345678", type: "phone" },
  ],
});

if (bulk.summary.failed > 0) {
  console.error("Partial failure:", bulk.errors);
}

const values = await veilio.detokenizeBulk({
  tokens: bulk.tokens.map((t) => ({
    token: t.token,
    reason: "Migration verification",
  })),
});
```

#### Retention and shredding

Schedule automatic shredding at tokenization time, or shred immediately:

```ts
// Automatic shredding after 30 days
const tokenized = await veilio.tokenize({
  data: "delete-me@example.com",
  type: "email",
  retention: { ttlDays: 30 },
});

// Immediate shredding (irreversible)
await veilio.shredToken({
  token: tokenized.token,
  reason: "User deletion request",
});
```

#### Error handling

```ts
import {
  VeilioClient,
  VeilioError,
  PlanLimitError,
  TokenShreddedError,
} from "@veilio/sdk";

try {
  const result = await veilio.detokenize({
    token: "tok_abc123...",
    reason: "Support case",
  });
} catch (error) {
  if (error instanceof TokenShreddedError) {
    // HTTP 410 — token was shredded, data is gone
    console.error("Token shredded:", error.message);
  } else if (error instanceof PlanLimitError) {
    // HTTP 403 — plan quota exceeded
    console.error("Plan limit:", error.message, error.details);
  } else if (error instanceof VeilioError) {
    console.error(`API error [${error.code}]:`, error.message);
    console.error("Status:", error.statusCode);
    console.error("Details:", error.details);
  } else {
    console.error("Unexpected error:", error);
  }
}
```

**Error codes**

| Code               | HTTP | Description                               |
| ------------------ | ---- | ----------------------------------------- |
| `AUTH_ERROR`       | 401  | Invalid or missing API key                |
| `VALIDATION_ERROR` | 400  | Invalid request data                      |
| `PLAN_LIMIT`       | 403  | Plan quota exceeded                       |
| `RATE_LIMIT_ERROR` | 429  | Rate limit exceeded                       |
| `TOKEN_SHREDDED`   | 410  | Token has been cryptographically shredded |
| `TIMEOUT_ERROR`    | 408  | Request timed out                         |
| `INTERNAL_ERROR`   | 5xx  | Server error                              |

#### TypeScript

Full type definitions are included:

```ts
import {
  VeilioClient,
  type TokenizeResult,
  type BulkTokenizeResult,
  type FormatTokenizeResult,
} from "@veilio/sdk";

const result: TokenizeResult = await veilio.tokenize({
  data: "test@example.com",
  type: "email",
});
```

#### Requirements

* Node.js 18+ (native `fetch`) or install `node-fetch` for older versions
* A valid Veilio API key

**Links**

* NPM package: <https://www.npmjs.com/package/@veilio/sdk>
* SDK style reference: <https://docs.veilio.xyz/documentation/getting-started/>


# SDK Python Integration

Install:

```bash
pip install veilio-sdk
```

Example:

```python
import os
from veilio_sdk import VeilioClient

veilio = VeilioClient(
    api_key=os.getenv("VEILIO_API_KEY"),
    base_url=os.getenv("VEILIO_BASE_URL", "https://app.veilio.xyz/api"),
)

def create_customer(email: str):
    result = veilio.tokenize(
        data=email,
        type="email",
    )

    # Store token in your DB, not the raw email.
    return {"email_token": result["token"]}

def send_welcome_email(email_token: str):
    original = veilio.detokenize(
        token=email_token,
        reason="Send welcome email",
    )
    return original["data"]

# Call when a user requests account deletion (GDPR right to erasure)
def delete_user_data(email_token: str):
    veilio.shred_token(
        token=email_token,
        reason="GDPR - user deletion request",
    )
```

#### Client configuration

```python
veilio = VeilioClient(
    api_key: str,              # Required — your Veilio API key
    base_url: str = "https://app.veilio.xyz/api",
    timeout: int = 30,         # Request timeout in seconds
    max_retries: int = 3,      # Retries on rate limits / network errors
)
```

For **on-premise** deployments, point `base_url` to your instance:

```python
veilio = VeilioClient(
    api_key=os.environ["VEILIO_API_KEY"],
    base_url="https://your-domain.com/api",
)
```

Environment variables:

| Variable          | Description                               |
| ----------------- | ----------------------------------------- |
| `VEILIO_API_KEY`  | API key from the Veilio dashboard         |
| `VEILIO_BASE_URL` | API base URL (optional, defaults to SaaS) |

#### API reference

**`tokenize(data, type=None, metadata=None, retention=None)`**

Tokenize a single field.

```python
result = veilio.tokenize(
    data="john@example.com",
    type="email",                          # Optional: email, phone, ssn, etc.
    metadata={"source": "signup"},         # Optional
    retention={                            # Optional — automatic shredding
        "ttlDays": 30,                     # Shred after N days
        # or "retentionUntil": "2026-12-31T23:59:59.000Z"
    },
)

# Returns: {"token": str, "createdAt": str, "retentionUntil": str | None}
```

**`tokenize_bulk(fields)`**

Tokenize multiple fields in one request.

```python
result = veilio.tokenize_bulk([
    {"data": "john@example.com", "type": "email"},
    {"data": "+33612345678", "type": "phone", "retention": {"ttlDays": 365}},
])

# Returns: {"tokens": [...], "summary": {"total", "success", "failed"}, "errors"?, "createdAt"}
```

**`detokenize(token, reason=None)`**

```python
result = veilio.detokenize(
    token="tok_abc123...",
    reason="Send email",  # Optional — logged for audit
)

# Returns: {"data": str, "accessedAt": str}
```

**`detokenize_bulk(tokens, reason=None)`**

```python
result = veilio.detokenize_bulk(
    [
        {"token": "tok_abc123...", "reason": "Profile view"},
        {"token": "tok_def456...", "reason": "Profile view"},
    ],
    reason="Profile view",  # Optional — one audit log line for the whole batch
)

# Returns: {"results": [...], "summary": {...}, "errors"?, "accessedAt"}
```

**`shred_token(token, reason=None)`**

Immediately and irreversibly destroy a token (cryptographic erasure).

```python
result = veilio.shred_token(
    token="tok_abc123...",
    reason="GDPR delete request",  # Optional
)

# Returns: {"token": str, "shreddedAt": str}
```

**`tokenize_format(data, format=None, options=None)`**

Tokenize structured data (JSON, CSV, or SQL).

```python
import json

result = veilio.tokenize_format(
    data=json.dumps({"email": "john@example.com", "phone": "+33612345678"}),
    format="json",  # Optional — auto-detected if omitted ("json", "csv", "sql")
    options={
        "fields": ["email"],              # Optional — tokenize only specific paths
        "retention": {"ttlDays": 365},    # Optional
        "csv": {                          # CSV-specific options
            "delimiter": ",",
            "hasHeaders": True,
        },
    },
)

# Returns: {"format", "tokenizedData", "tokens": [{"path", "token", "type?"}], "summary", "metadata"?}
```

**`detokenize_format(tokenized_data, format, tokens)`**

Restore original values in structured data.

```python
result = veilio.detokenize_format(
    tokenized_data='{"email": "tok_abc123..."}',
    format="json",
    tokens=[{"path": "email", "token": "tok_abc123..."}],
)

# Returns: {"format", "detokenizedData", "summary": {"tokensProcessed"}}
```

#### Bulk operations

For imports, migrations, or high-throughput jobs:

* `tokenize_bulk`
* `detokenize_bulk`

Use batching and retry logic for `429` responses. The SDK retries automatically (respecting `Retry-After` headers) up to `max_retries` times.

```python
fields = [
    {"data": "alice@example.com", "type": "email"},
    {"data": "+33612345678", "type": "phone"},
]

bulk = veilio.tokenize_bulk(fields=fields)

if bulk["summary"]["failed"] > 0:
    print("Partial failure:", bulk.get("errors", []))

tokens = [
    {"token": t["token"], "reason": "Migration verification"}
    for t in bulk["tokens"]
]
values = veilio.detokenize_bulk(tokens=tokens)
```

#### Retention and shredding

Schedule automatic shredding at tokenization time, or shred immediately:

```python
# Automatic shredding after 30 days
tokenized = veilio.tokenize(
    data="delete-me@example.com",
    type="email",
    retention={"ttlDays": 30},
)

# Immediate shredding (irreversible)
veilio.shred_token(
    token=tokenized["token"],
    reason="User deletion request",
)
```

#### Error handling

Handle Veilio SDK errors explicitly:

```python
from veilio_sdk import (
    VeilioClient,
    VeilioError,
    AuthenticationError,
    RateLimitError,
    PlanLimitError,
    TokenShreddedError,
)

try:
    result = veilio.detokenize(token="tok_abc123...", reason="Support case")
except AuthenticationError as e:
    print(f"Auth error: {e}")
except RateLimitError as e:
    print(f"Rate limit reached, retry after: {getattr(e, 'retry_after', None)}s")
except PlanLimitError as e:
    print(f"Plan limit exceeded: {e}")
except TokenShreddedError:
    print("Token has been shredded and cannot be detokenized.")
except VeilioError as e:
    print(f"Veilio API error [{e.code}]: {e.message}")
```

**Error codes**

| Code               | HTTP | Description                               |
| ------------------ | ---- | ----------------------------------------- |
| `AUTH_ERROR`       | 401  | Invalid or missing API key                |
| `VALIDATION_ERROR` | 400  | Invalid request data                      |
| `PLAN_LIMIT`       | 403  | Plan quota exceeded                       |
| `RATE_LIMIT_ERROR` | 429  | Rate limit exceeded                       |
| `TOKEN_SHREDDED`   | 410  | Token has been cryptographically shredded |
| `TIMEOUT_ERROR`    | 408  | Request timed out                         |
| `INTERNAL_ERROR`   | 5xx  | Server error                              |

#### Requirements

* Python 3.8+
* `requests` library (installed automatically with `veilio-sdk`)
* A valid Veilio API key

**Links**

* PyPI package: <https://pypi.org/project/veilio-sdk/>
* SDK style reference: <https://docs.veilio.xyz/documentation/getting-started/>


# HTTP Integration

Use direct HTTP calls if you need full control.

Use direct HTTP calls when you need full control, a language without an official SDK, or custom retry/idempotency logic.

#### Authentication

Pass your API key in one of the following headers:

* `Authorization: Bearer <API_KEY>`
* `X-API-Key: <API_KEY>`

Never expose the key in browser code, mobile apps, or public repositories.

#### Base URL

| Environment       | URL                          |
| ----------------- | ---------------------------- |
| SaaS production   | `https://app.veilio.xyz/api` |
| On-premise        | `https://<your-domain>/api`  |
| Local development | `http://localhost:3000/api`  |

> Use `https://app.veilio.xyz/api`, not `api.veilio.com`.

#### Supported data types (`type` field)

The `type` field is optional but recommended for audit, SIEM events, and dashboard filtering.

| Type                     | Example                              |
| ------------------------ | ------------------------------------ |
| `email`                  | `john@example.com`                   |
| `phone`                  | `+33612345678`                       |
| `ssn`                    | `123-45-6789`                        |
| `iban`                   | `FR7630006000011234567890189`        |
| `address`                | `10 rue de la Paix, Paris`           |
| `firstName` / `lastName` | Personal names                       |
| `birthdate`              | `1990-01-15`                         |
| Custom string            | Any label your app uses consistently |

***

#### Core endpoints

| Method | Path                 | Description                |
| ------ | -------------------- | -------------------------- |
| POST   | `/tokenize`          | Single field               |
| POST   | `/tokenize/bulk`     | Batch fields               |
| POST   | `/tokenize/format`   | JSON / CSV / SQL in-place  |
| POST   | `/detokenize`        | Reveal one token           |
| POST   | `/detokenize/bulk`   | Reveal many tokens         |
| POST   | `/detokenize/format` | Restore structured data    |
| POST   | `/tokens/shred`      | Irreversible crypto-shred  |
| POST   | `/flows/ingest`      | Form / lead JSON ingestion |

See the full API Reference for dashboard, dataset, and compliance routes.

***

#### Tokenize

```bash
curl -s -X POST "$VEILIO_BASE_URL/tokenize" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": "support@veilio.xyz",
    "type": "email",
    "metadata": { "entityId": "customer_42", "source": "signup" },
    "retention": { "ttlDays": 365 }
  }'
```

**Request body**

| Field       | Required | Description                                                               |
| ----------- | -------- | ------------------------------------------------------------------------- |
| `data`      | Yes      | Sensitive string to tokenize                                              |
| `type`      | No       | Data category (see table above)                                           |
| `metadata`  | No       | Arbitrary JSON (e.g. `entityId` for profile grouping)                     |
| `retention` | No       | `{ "ttlDays": 30 }` or `{ "retentionUntil": "2026-12-31T23:59:59.000Z" }` |

**Response `200`**

```json
{
  "token": "tok_abc123...",
  "createdAt": "2026-03-26T10:00:00.000Z",
  "retentionUntil": "2027-03-26T10:00:00.000Z"
}
```

***

#### Detokenize

```bash
curl -s -X POST "$VEILIO_BASE_URL/detokenize" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "tok_abc123...",
    "reason": "Support ticket resolution"
  }'
```

**Response `200`**

```json
{
  "data": "support@veilio.xyz",
  "accessedAt": "2026-03-26T10:01:00.000Z"
}
```

**Response `410`** — token was cryptographically shredded (`TOKEN_SHREDDED`).

***

#### Bulk tokenize

```bash
curl -s -X POST "$VEILIO_BASE_URL/tokenize/bulk" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": [
      { "data": "support@veilio.xyz", "type": "email" },
      { "data": "+33601020304", "type": "phone", "retention": { "ttlDays": 90 } }
    ]
  }'
```

**Response `200`**

```json
{
  "tokens": [
    { "token": "tok_...", "type": "email", "createdAt": "...", "retentionUntil": null },
    { "token": "tok_...", "type": "phone", "createdAt": "...", "retentionUntil": "..." }
  ],
  "summary": { "total": 2, "success": 2, "failed": 0 },
  "createdAt": "2026-03-26T10:00:00.000Z"
}
```

Partial failures include an `errors` array with `{ "field": 0, "error": "..." }`.

***

#### Bulk detokenize

```bash
curl -s -X POST "$VEILIO_BASE_URL/detokenize/bulk" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tokens": [
      { "token": "tok_abc...", "reason": "Profile view" },
      { "token": "tok_def...", "reason": "Profile view" }
    ],
    "reason": "Profile view"
  }'
```

The top-level `reason` is optional — it creates one audit log line for the whole batch.

**Response `200`**

```json
{
  "results": [
    { "token": "tok_abc...", "data": "support@veilio.xyz", "accessedAt": "..." }
  ],
  "summary": { "total": 2, "success": 2, "failed": 0 },
  "accessedAt": "2026-03-26T10:01:00.000Z"
}
```

***

#### Tokenize format (JSON, CSV, SQL)

```bash
curl -s -X POST "$VEILIO_BASE_URL/tokenize/format" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": "{\"email\":\"john@example.com\",\"phone\":\"+33612345678\"}",
    "format": "json",
    "options": {
      "fields": ["email"],
      "retention": { "ttlDays": 365 }
    },
    "entityId": "customer_42"
  }'
```

**Response `200`**

```json
{
  "format": "json",
  "tokenizedData": "{\"email\":\"tok_...\",\"phone\":\"+33612345678\"}",
  "tokens": [
    { "path": "email", "token": "tok_...", "type": "email" }
  ],
  "summary": { "totalFields": 2, "tokenizedFields": 1 }
}
```

CSV options: `options.csv.delimiter`, `options.csv.hasHeaders`.

***

#### Detokenize format

```bash
curl -s -X POST "$VEILIO_BASE_URL/detokenize/format" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tokenizedData": "{\"email\":\"tok_abc123...\"}",
    "format": "json",
    "tokens": [{ "path": "email", "token": "tok_abc123..." }]
  }'
```

**Response `200`**

```json
{
  "format": "json",
  "detokenizedData": "{\"email\":\"john@example.com\"}",
  "summary": { "tokensProcessed": 1 }
}
```

***

#### Shred a token

Immediately and irreversibly destroy a token (GDPR erasure, right to be forgotten).

```bash
curl -s -X POST "$VEILIO_BASE_URL/tokens/shred" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "tok_xxxxxxxxxxxx",
    "reason": "GDPR - user deletion request"
  }'
```

**Response `200`**

```json
{
  "token": "tok_xxxxxxxxxxxx",
  "shreddedAt": "2026-03-26T10:05:00.000Z"
}
```

***

#### Flow ingest (form / lead payloads)

Tokenize a JSON object in one call — useful for signup forms, CRM webhooks, or lead capture. Uses the tokenization schema attached to your API key (if configured).

```bash
curl -s -X POST "$VEILIO_BASE_URL/flows/ingest" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flowId": "signup-v1",
    "source": "website",
    "entityId": "lead_9912",
    "data": {
      "email": "john@example.com",
      "phone": "+33612345678",
      "company": "Acme Corp"
    },
    "metadata": { "utm_source": "google" }
  }'
```

**Response `201`**

```json
{
  "flowId": "signup-v1",
  "protectedData": {
    "email": "tok_...",
    "phone": "tok_...",
    "company": "Acme Corp"
  },
  "tokens": [
    { "path": "email", "token": "tok_...", "type": "email" },
    { "path": "phone", "token": "tok_...", "type": "phone" }
  ],
  "lookup": {
    "email_hash": "...",
    "phone_hash": "..."
  },
  "policyApplied": "api_key_schema",
  "metadata": { "source": "website", "originalMetadata": { "utm_source": "google" } }
}
```

Store `protectedData` in your database. Fields not covered by your API key schema remain in plaintext.

***

#### Response and retry strategy

| HTTP | Code               | Action                                   |
| ---- | ------------------ | ---------------------------------------- |
| 401  | `AUTH_ERROR`       | Fix or rotate API key                    |
| 400  | `VALIDATION_ERROR` | Fix request body                         |
| 403  | `PLAN_LIMIT`       | Check usage / upgrade plan               |
| 410  | `TOKEN_SHREDDED`   | Data is gone — do not retry              |
| 429  | `RATE_LIMIT_ERROR` | Honor `Retry-After`, exponential backoff |
| 5xx  | `INTERNAL_ERROR`   | Retry with backoff                       |

* Handle `429` with exponential backoff.
* Read the `Retry-After` header before retrying.
* Use bulk endpoints for high-throughput imports.
* Add idempotency in your app for replayed business operations.

**Related links**

* SDK Javascript · SDK Python
* API Reference
* Troubleshooting


# Veilio Use Cases

Veilio protects sensitive data by replacing raw values (email, phone, IDs, addresses…) with tokens, and by enforcing strict, auditable access when teams need to see the real value. Below are the most

<figure><img src="https://871091000-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0WqIAiGHfaanrggvU6xD%2Fuploads%2FA2L4PG22EVP0IpdynPJP%2F1774360656396.jpeg?alt=media&amp;token=58d27edb-43cd-4c53-a35a-8a5ed7594cc5" alt=""><figcaption></figcaption></figure>

***

#### 1) Account profile page

Situation\
Your user profile stores sensitive fields (email, phone number, address, IDs…).

With Veilio

* On write (sign‑up, profile update):
  * Backend calls `POST /tokenize` for sensitive fields.
  * Database stores `tok_...` instead of plaintext.
* On read (display profile, send notification):
  * Backend calls `POST /detokenize` only when needed.
  * UI never talks directly to Veilio.

Why it matters

* A SQL dump of the `users` table no longer exposes PII in clear.
* You keep the same UI and UX; only the storage path changes.
* You can attach retention and crypto‑shredding policies per token.

***

#### 2) Contact form / demo request form

Situation\
Your marketing/website form collects lead data (email, phone, company…).

With Veilio

* On form submit:
  * Your edge/API layer calls Veilio to tokenize email/phone.
  * Marketing tools/CRM/storefront receive tokens only.
* Lead enrichment / routing uses tokens as identifiers.
* When a verified system needs the real email (e.g. send an email), your backend detokenizes with a specific reason (`"support"`, `"marketing_campaign"`, etc.).

Why it matters

* These are often the most exposed flows (internet‑facing, unauthenticated).
* You reduce the blast radius of landing page leaks or misconfigurations.
* Great first “quick win” to validate Veilio on a visible but low‑risk surface.

***

#### 3) Legacy CSV & historical imports

Situation\
You already have historical exports (CRM files, CSVs, JSON dumps, old imports) containing PII.

With Veilio

* Upload existing datasets from the dashboard:
  * CSV: classic header + rows.
  * JSON: an array of objects (e.g. `[{"email":"..."},{"email":"..."}]`).
* For each dataset:
  * Veilio builds a tokenized grid of values.
  * You define column policies (which roles / users can see which columns).
* Consumers download a filtered, detokenized CSV when needed.

Why it matters

* You reduce risk on already existing sensitive archives without rewriting the app.
* You get a highly demonstrative POC (“before/after” on real data).
* It’s a natural bridge to analytics/BI use cases.

***

#### 4) Shared analytics datasets (Dataset Access Tokens)

Situation\
Data and BI teams (and sometimes partners) need to consume datasets for analysis: usage events, logs, customer cohorts, medical metrics, etc.

With Veilio

* Owner/DPO:
  * uploads a dataset (CSV or JSON),
  * defines column visibility policies per role/user,
  * generates a Dataset Access Token with a specific TTL (e.g. `1h` or `24h`).
* BI / data engineers:
  * use a simple `curl` or ETL job with header:
    * `x-veilio-dataset-access-token: dsat_...`
  * download a CSV where:
    * only allowed columns are present,
    * token values are detokenized under the hood when permitted.

Why it matters

* You avoid spreading raw dumps in S3 buckets, laptops, and ad‑hoc scripts.
* Dataset access is:
  * scoped (dataset, org, user/role),
  * time‑bound (short‑lived token),
  * auditable.
* Fits very well teams like Medadom’s BI / ops / data units.

***

#### 5) GDPR exports & data subject access

Situation\
You need to provide users with a copy of their personal data (GDPR “right of access”), but a naive export from the database is too risky.

With Veilio

* Authenticated user requests export from the dashboard.
* Backend calls `POST /api/compliance/export` with:
  * `format` (`json` or `csv`),
  * a valid 2FA/TOTP code.
* Veilio:
  * verifies session + 2FA,
  * builds the export (detokenized where allowed),
  * encrypts the file at rest,
  * sends a one‑time download link to the user.

Why it matters

* DSAR / GDPR workflows become repeatable and auditable.
* Export files are:
  * encrypted at rest,
  * downloadable once,
  * protected by session + token expiry.
* You don’t leave “full plaintext exports” lying around indefinitely.

***

#### 6) Database dumps & technical environments (UC8)

Situation\
Engineering and ops teams:

* create database dumps,
* refresh staging/QA from production,
* keep snapshots for debugging.

Without tokenization, these dumps often contain full PII.

With Veilio

* In production:
  * sensitive columns are already tokenized, so dumps contain tokens.
* Before promoting a dump:
  * a dump checker (policy + script) scans for plaintext PII patterns (emails, phone, IBAN, cards, etc.),
  * if any disallowed match is found, the pipeline fails and the dump is not used.
* In staging/QA:
  * teams work with tokens,
  * any detokenization must go through Veilio with proper roles + audit.

Why it matters

* You keep the flexibility of dump/restore workflows without turning every dump into a compliance incident waiting to happen.
* Staging/QA can operate on realistic data shapes while remaining privacy‑preserving.
* It aligns dev/ops practices with your privacy‑by‑design posture.

***

#### 7) CRM & tools with native read access (Phase 2)

Situation\
Sales, support or medical teams need to view data directly inside CRMs or vertical tools (HubSpot, Salesforce, Zendesk, EMR…).

With Veilio

* Phase 1: you tokenise what’s stored in the CRM database (imports, sync).
* Phase 2:
  * browser extension or native plugin,
  * tokenization on input, detokenization on display via Veilio’s API, with role‑based controls.

Why it matters

* You upgrade highly exposed tools (CRMs, helpdesks) without disrupting daily workflows.
* Deep business value once you’re comfortable with the core tokenization flows.

***

#### In short

If you want to validate Veilio quickly, the typical entry points are:

* Profile page (core product),
* Contact / demo forms (public web),
* Legacy CSV / analytics datasets (batch/BI),
* GDPR exports (compliance),
* DB dumps (ops/dev).

### Related links

* Product website: <https://veilio.xyz>
* Contact / Demo: <https://veilio.xyz/contact>


# Token Database: Search & Profiles

The **Tokenized database** page (`/dashboard/database`) is an advanced browser for tokens you are allowed to see (RBAC + organization policies).

### Search by plaintext value (blind index)

Find tokens without storing searchable plaintext in your app database:

1. Enter a value (e.g. email) and optional type filter.
2. Veilio hashes the value with your user key and matches `lookupHash` in the Clients DB.
3. Results respect the same visibility rules as the token list.

API (session only):

```http
POST /api/tokens/lookup
Content-Type: application/json

{
  "value": "user@example.com",
  "type": "email"
}
```

> Search actions are audited. The searched value is **never** written to audit logs.

### Search by profile (`entityId`)

Group tokens belonging to the same person or business record:

* Set `entityId` when tokenizing (`metadata.entityId` or `entityIdColumn` on datasets / bulk / format).
* Filter the database view by `entityId` to see all tokens for one profile.

### Bulk reveal

Select rows and **Reveal** to detokenize up to 100 tokens at once (`POST /api/tokens/reveal-bulk`). Requires write access (not VIEWER).

### Crypto-shred (destroy)

| Action               | Scope                           | API                                                                       |
| -------------------- | ------------------------------- | ------------------------------------------------------------------------- |
| Shred one token      | Single row                      | `DELETE /api/tokens/[id]` (session) or `POST /api/tokens/shred` (API key) |
| Shred selection      | Up to 200 IDs                   | `POST /api/tokens/shred-bulk` with `{ "ids": [...] }`                     |
| Shred entire profile | All tokens with same `entityId` | `POST /api/tokens/shred-bulk` with `{ "entityId": "customer_42" }`        |

Shredding is **irreversible**: encrypted payload and blind index are erased.

The dashboard asks for confirmation; profile shred requires typing the `entityId`.

### Visibility consistency

List, search, reveal, and shred all use `buildTokenVisibility` so a user never sees tokens outside their org role, dataset policies, or VIEWER whitelist.


# Data Lifecycle: Retention and Crypto Shredding

Veilio supports lifecycle control for tokenized data through retention metadata and irreversible crypto shredding.

> **Important:** setting `retention` at tokenize time only **schedules** destruction. Tokens are shredded automatically **only if** a job calls `/api/internal/tokens/auto-shred` on a schedule. Without that job, expired tokens remain detokenizable.

***

#### Retention at tokenization time

When tokenizing data, you can define retention via:

| Field            | Type         | Description                                           |
| ---------------- | ------------ | ----------------------------------------------------- |
| `ttlDays`        | positive int | Shred after N days from tokenization                  |
| `retentionUntil` | ISO datetime | Absolute shred date (e.g. `2027-01-01T00:00:00.000Z`) |

Provide **one** of the two. Example:

```bash
curl -s -X POST "$VEILIO_BASE_URL/tokenize" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": "john@example.com",
    "type": "email",
    "retention": { "ttlDays": 90 }
  }'
```

Response includes the resolved date:

```json
{
  "token": "tok_abc123...",
  "createdAt": "2026-07-26T08:00:00.000Z",
  "retentionUntil": "2026-10-24T08:00:00.000Z"
}
```

The same `retention` object works with `/tokenize/bulk`, `/tokenize/format`, and the JS/Python SDKs.

***

#### Auto-shredding (required for TTL / retentionUntil)

Internal route processes expired tokens in batches:

|          |                                               |
| -------- | --------------------------------------------- |
| Endpoint | `POST /api/internal/tokens/auto-shred`        |
| Auth     | `Authorization: Bearer $VEILIO_CRON_SECRET`   |
| Query    | `limit` (optional, default `500`, max `5000`) |

**What it does:** finds tokens where `retentionUntil <= now` and `shreddedAt` is null, then crypto-shreds them (encrypted payload cleared, detokenization returns `410 TOKEN_SHREDDED`).

**Configure the secret**

In your environment (required on **on-premise**; already set on Veilio SaaS):

```bash
VEILIO_CRON_SECRET=$(openssl rand -base64 32)
```

**Schedule the job**

Recommended: **hourly**. Example crontab on the host:

```cron
0 * * * * curl -fsS -X POST "https://your-domain.com/api/internal/tokens/auto-shred?limit=500" \
  -H "Authorization: Bearer $VEILIO_CRON_SECRET" \
  >> /var/log/veilio-auto-shred.log 2>&1
```

One-shot test:

```bash
curl -s -X POST "https://your-domain.com/api/internal/tokens/auto-shred?limit=500" \
  -H "Authorization: Bearer $VEILIO_CRON_SECRET"
```

Success response:

```json
{
  "processed": 12,
  "limit": 500,
  "executedAt": "2026-07-26T09:00:01.000Z"
}
```

If more than `limit` tokens expire at once, the next cron run continues the backlog.

| Environment                 | Who runs the cron?                                      |
| --------------------------- | ------------------------------------------------------- |
| **SaaS** (`app.veilio.xyz`) | Veilio — you only pass `retention` when tokenizing      |
| **On-premise**              | **You** — set `VEILIO_CRON_SECRET` and schedule the job |

See also: [Installation guide](https://docs.veilio.xyz/documentation/on-premise/installation-guide) (on-premise env + ops checklist).

***

#### On-demand shredding

Use `POST /api/tokens/shred` to shred a token immediately with an API key (GDPR erasure, early deletion, etc.).

```bash
curl -X POST "https://app.veilio.xyz/api/tokens/shred" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"token": "tok_xxxxxxxxxxxx", "reason": "GDPR - user deletion request"}'
```

On-premise: replace the host with your instance URL (`https://your-domain.com/api/tokens/shred`).

> Do **not** use `DELETE /api/tokens/[id]` — that route is dashboard-only (browser session required) and will always return `401` when called with an API key.

**Request body**

| Field    | Type   | Required | Description                                       |
| -------- | ------ | -------- | ------------------------------------------------- |
| `token`  | string | Yes      | The `tok_...` value returned at tokenization time |
| `reason` | string | No       | Audit log label (max 500 chars)                   |

**Success (`200`)**

```json
{
  "token": "tok_xxxxxxxxxxxx",
  "shreddedAt": "2026-05-06T16:13:00.000Z"
}
```

**Error codes**

| HTTP | Code               | Cause                                       |
| ---- | ------------------ | ------------------------------------------- |
| 401  | `AUTH_ERROR`       | Missing, invalid, or revoked API key        |
| 404  | `NOT_FOUND`        | Token not found                             |
| 403  | `FORBIDDEN`        | Token belongs to a different organization   |
| 400  | `VALIDATION_ERROR` | Missing `token` field or invalid body shape |

SDK equivalents: `client.shredToken({ token, reason })` (JS) / `client.shred_token(token, reason=...)` (Python).

***

#### Detokenization behavior after shredding

Once shredded, detokenization returns:

* HTTP status: `410`
* code: `TOKEN_SHREDDED`

The operation is irreversible: encrypted data and search index are cleared.

***

#### Operational guidance

* Use retention defaults for sensitive classes (email / phone / ID).
* Keep shred `reason` values meaningful for auditability.
* On-premise: treat auto-shred as a **production go-live requirement** if you use `ttlDays` / `retentionUntil`.
* Monitor cron logs (`processed` count) so a silent auth failure (`401`) does not leave expired data live.
* Prefer hourly (or more frequent) runs for predictable compliance; increase `limit` if you ingest large batches with short TTLs.

***

#### Related links

* [SDK Javascript](https://docs.veilio.xyz/documentation/getting-started/sdk-js)
* [SDK Python](https://docs.veilio.xyz/documentation/getting-started/sdk-python)
* [HTTP Integration](https://docs.veilio.xyz/documentation/getting-started/http-integration)
* [Installation guide (on-premise)](https://docs.veilio.xyz/documentation/on-premise/installation-guide)
* [Go-live Checklist](https://docs.veilio.xyz/documentation/help/golive-check)


# Dataset Access Tokens

Dataset Access Tokens are short-lived signed tokens designed for controlled dataset sharing. They are scoped and safer than long-lived static credentials for one-off dataset access from terminal scrip

### Why this exists

* Limit token lifetime by default
* Scope access to one dataset
* Preserve role-based column filtering
* Reduce risk if a token leaks

### How it works

1. Owner or DPO generates/rotates a dataset share key from the dashboard.
2. Veilio returns a short-lived Dataset Access Token.
3. Consumer calls dataset download endpoint with the token header.
4. Veilio enforces:
   * token signature validity
   * expiration (`exp`)
   * dataset scope
   * organization scope
   * role-based column policies

### Header and token format

* Header: `x-veilio-dataset-access-token`
* Token prefix: `dsat_...`

### TTL defaults and limits

* Default TTL: 15 minutes
* Configurable presets (dashboard): `15m`, `1h`, `8h`, `24h`, `7d`
* Max TTL (server side): 24h in the current token utility (unless overridden in code/policy)

### Example (curl)

```bash
curl -sSf \
  -H "x-veilio-dataset-access-token: dsat_xxx.yyy" \
  "https://app.veilio.xyz/api/datasets/<DATASET_ID>/download" \
  -o export.csv
```

### Security notes

* Use HTTPS only.
* Never store tokens in source control.
* Prefer short TTL for ad-hoc/manual use.
* Rotate tokens after partner offboarding or scope change.

### Personal datasets (no organization required)

#### Freemium, Starter, and Pro plans can upload datasets without being part of an organization. Column policies and org-wide sharing (share key, member access) still require an organization.

#### Secure one-time download links

Share a dataset with someone who has no Veilio account via a single-use signed URL.&#x20;

#### **Create a share link**

```bash
curl -X POST "https://app.veilio.xyz/api/datasets/{DATASET_ID}/public-share" \
  -H "Cookie: <session>" \
  -H "Content-Type: application/json" \
  -d '{
    "columnsToExpose": ["email", "first_name"],
    "ttlPreset": "24h",
    "recipientEmail": "partner@example.com"
  }'
```

| Field             | Type      | Required | Description                                                 |
| ----------------- | --------- | -------- | ----------------------------------------------------------- |
| `columnsToExpose` | string\[] | ✅        | Columns to deliver in plaintext. Other columns are omitted. |
| `ttlPreset`       | string    | ✅        | `15m` · `1h` · `8h` · `24h` · `7d`                          |
| `recipientEmail`  | string    | ❌        | If provided, an email with the link is sent automatically.  |

Response

```
{
"shareUrl": "https://app.veilio.xyz/api/datasets/{id}/download?public_token=...",
"expiresAt": "2026-05-07T16:00:00.000Z"
}
```

The recipient opens the link in any browser or `curl` — no Veilio account needed. The link is single-use: it is permanently revoked after the first download. After expiry or first use, the endpoint returns `410 Gone`.

***

#### Dataset access token revocation

Starting from token format v4, rotating the share key from the dashboard immediately invalidates all `dsat_` tokens previously issued with the old key — no need to wait for their natural expiry.

This means: if a partner is offboarded or a key is compromised, regenerating the share key is sufficient to cut off access instantly.<br>


# Datasets Upload: CSV and JSON

Veilio datasets support both CSV and JSON upload formats.

Veilio datasets let you upload structured files, tokenize sensitive columns, and share filtered views with role-based access policies.

#### Supported formats

* **CSV** (`.csv`)
* **JSON** (`.json`) — array of objects

**Expected JSON shape**

```json
[
  { "email": "a@example.com", "phone": "+33600000000", "customer_id": "C001" },
  { "email": "b@example.com", "phone": "+33700000000", "customer_id": "C002" }
]
```

***

#### Upload flow

1. Owner or DPO uploads a file from the dashboard **or** via API.
2. Veilio extracts headers / columns from CSV or JSON.
3. `columnPolicies` must include **every** column exactly once.
4. Non-empty cell values are tokenized into the dataset grid.
5. A dataset version is created and available for controlled download.

**Identity column (`entityId`)**

Specify which column holds your business identifier (kept in clear text, links tokens to a profile):

* **Dashboard:** set during upload configuration.
* **API:** `entityIdColumn` in the multipart form (must match a column name).

**Historical migration upload**

For large legacy imports, pass `migration=true` so tokenized cells consume the **migration budget** instead of monthly plan quota.

Requirements:

* Migration mode activated (`POST /api/onboarding/migration`).
* Active Starter / Pro / Enterprise plan (or MIGRATION API key).

***

#### API upload (multipart)

**Endpoint:** `POST /api/datasets`

**Auth:** API key (`Authorization: Bearer` or `X-API-Key`) or dashboard session.

**Role:** Owner or DPO only.

**Content-Type:** `multipart/form-data`

| Form field       | Required | Description                                              |
| ---------------- | -------- | -------------------------------------------------------- |
| `file`           | Yes      | CSV or JSON file                                         |
| `name`           | Yes      | Dataset name (1–200 chars)                               |
| `columnPolicies` | Yes      | JSON **string** — one policy per column                  |
| `format`         | No       | `csv` or `json` (auto-detected from filename if omitted) |
| `description`    | No       | Max 2000 chars                                           |
| `entityIdColumn` | No       | Column name for business ID (plaintext)                  |
| `migration`      | No       | `"true"` to use migration budget                         |

**Example `columnPolicies`**

```json
{
  "customer_id": { "roles": ["OWNER", "DPO", "MEMBER", "VIEWER"] },
  "email": { "roles": ["OWNER", "DPO"] },
  "phone": { "roles": ["OWNER", "DPO", "MEMBER"] }
}
```

* `roles`: which organization roles may see this column on download.
* `userIds`: optional list of specific user IDs (alternative to roles).

Every column in the file **must** have a policy entry. Extra or missing keys return `VALIDATION_ERROR`.

**curl example**

```bash
export VEILIO_API_KEY="veil_live_xxxxxxxx"
export VEILIO_BASE_URL="https://app.veilio.xyz/api"

curl -s -X POST "$VEILIO_BASE_URL/datasets" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -F "file=@contacts.csv" \
  -F "name=CRM Export March 2026" \
  -F 'description=Historical customer export' \
  -F 'entityIdColumn=customer_id' \
  -F 'migration=true' \
  -F 'columnPolicies={
    "customer_id": { "roles": ["OWNER", "DPO", "MEMBER", "VIEWER"] },
    "email": { "roles": ["OWNER", "DPO"] },
    "phone": { "roles": ["OWNER", "DPO", "MEMBER"] }
  }'
```

**JSON file example**

```bash
curl -s -X POST "$VEILIO_BASE_URL/datasets" \
  -H "Authorization: Bearer $VEILIO_API_KEY" \
  -F "file=@contacts.json;type=application/json" \
  -F "name=Contacts JSON" \
  -F 'format=json' \
  -F 'columnPolicies={"email":{"roles":["OWNER","DPO"]},"phone":{"roles":["OWNER","DPO","MEMBER"]}}'
```

**Success response `201`**

```json
{
  "id": "ds_...",
  "name": "CRM Export March 2026",
  "version": 1,
  "byteSize": 1048576,
  "columnCount": 3,
  "storage": "tokenized",
  "shareApiKey": "veil_...",
  "shareApiKeyPrefix": "veil_ab",
  "datasetAccessToken": "dsat_...",
  "datasetAccessTokenExpiresAt": "2026-03-28T10:00:00.000Z",
  "datasetAccessTokenHeader": "x-veilio-dataset-access-token"
}
```

`shareApiKey` and `datasetAccessToken` are returned only for **organization** datasets.

***

#### Validation rules and limits

| Rule             | Default / detail                                           |
| ---------------- | ---------------------------------------------------------- |
| Max file size    | **320 MB** (`VEILIO_DATASET_MAX_BYTES`)                    |
| Max cells        | **200 000** (`headers × rows`, `VEILIO_DATASET_MAX_CELLS`) |
| Empty file       | Rejected                                                   |
| `columnPolicies` | Must match columns exactly                                 |
| JSON shape       | Non-empty array of objects                                 |

#### Common errors

| Message                                    | Cause                           |
| ------------------------------------------ | ------------------------------- |
| `JSON dataset must be an array of objects` | Invalid JSON structure          |
| `JSON dataset is empty`                    | No rows                         |
| `Missing policy for column "..."`          | Incomplete `columnPolicies`     |
| `Policy references unknown column "..."`   | Extra policy key                |
| `Too many cells (max 200000)`              | File too large in cell count    |
| `File exceeds max size (... bytes)`        | File too large in bytes         |
| `Only Owner or DPO can upload datasets`    | Insufficient role               |
| `PLAN_LIMIT`                               | Dataset or token quota exceeded |
| Migration budget exhausted / expired       | When `migration=true`           |

***

#### Download behavior

Downloaded datasets are filtered by role and column policy. Consumers only receive columns they are allowed to see.

For programmatic download, use a Dataset Access Token (`dsat_...`) in header `x-veilio-dataset-access-token`.

**Related links**

* Dataset Access Tokens
* Historical Migration Mode
* API Reference


# Organizations & Teams

Veilio organizations let you **collaborate** on tokenized data: shared datasets, column-level access policies, member roles, and invitations.

> Organizations are **not** Enterprise-only. Freemium, Starter, and Pro accounts can create an org. What changes by plan is **how many members** you can invite.

### Roles

| Role     | UI label | See org data                                       | Write (tokenize) | Manage members | Billing |
| -------- | -------- | -------------------------------------------------- | ---------------- | -------------- | ------- |
| `OWNER`  | Owner    | All                                                | Yes              | Yes            | Yes     |
| `DPO`    | Owner    | All                                                | Yes              | Yes            | No      |
| `MEMBER` | Member   | Own tokens + shared dataset columns                | Yes              | No             | No      |
| `VIEWER` | Invitee  | Whitelisted API keys + shared datasets (read-only) | No               | No             | No      |

### Member limits by plan

| Plan       | Max members in org                 |
| ---------- | ---------------------------------- |
| Freemium   | 1 (owner only)                     |
| Starter    | 1 (owner only — **cannot invite**) |
| Pro        | 2 (owner + 1 collaborator)         |
| Enterprise | Unlimited                          |

To share datasets **inside Veilio** with colleagues, you need at least **Pro** (2 people) or **Enterprise** (larger teams).

### Dataset sharing within an organization

1. **Create an organization** (dashboard → Organization, or admin-provisioned).
2. **Upload a dataset** as OWNER or DPO while belonging to the org.
3. Veilio attaches the dataset to `organizationId` and creates an internal share API key.
4. Configure **column policies** per role/user (who sees which columns on download).
5. Invite members (DPO, MEMBER, VIEWER) — they access allowed columns only.

**Personal datasets** (no org): upload works on all plans, but **org-wide sharing** (member access, share keys) requires an organization.

### Invitations

* Only **OWNER** and **DPO** can invite.
* `POST /api/organization/invite` with `email` and `role`.
* Invitees receive an email link (7-day expiry).
* Alternative: join with a temporary invite code (`/api/organization/join-by-code`).

If the member limit is reached, the API returns `PLAN_LIMIT_REACHED` with the current plan name.

### VIEWER whitelist

VIEWER members only see tokens from API keys explicitly whitelisted for their membership. Configure from the organization settings UI.

### Public share (no Veilio account)

For external recipients (e.g. GDPR Article 15 delivery), use **one-time public download links** — see the existing **Dataset Access Tokens** page (public-share section). This is independent of org member count.

### Recommended setup (Pro + Stripe)

1. Admin creates the org and assigns the client as **OWNER**.
2. Client subscribes to **Pro** while already in the org (so Stripe links `organizationId` on the subscription).
3. Owner invites one collaborator (DPO, MEMBER, or VIEWER).


# Historical Migration Mode

Import a large existing database (e.g. 150k contacts from a CRM export) without consuming your monthly plan quota. Day-to-day operations after migration still use your normal plan limits (Starter, Pro

### When to use it

| Scenario                                         | Use migration mode?                 |
| ------------------------------------------------ | ----------------------------------- |
| One-time CSV/JSON import of historical PII       | ✅ Yes                               |
| Ongoing tokenization in production (forms, APIs) | ❌ No — use a standard API key       |
| GDPR full export when leaving Veilio             | ❌ No — export is already quota-free |

### How it works

1. **Activate migration** from the dashboard (`/dashboard/onboarding` → « Migration mode » card, or `POST /api/onboarding/migration`).
2. Veilio creates a **one-shot budget** on your subscription: `migrationAllowance`, `migrationTokensUsed`, `migrationExpiresAt`.
3. A dedicated API key with `purpose = MIGRATION` is generated (**shown once**).
4. Requests signed with this key (or dataset uploads with `migration=true`) consume the **migration budget**, not monthly tokens/requests.
5. When the budget is exhausted or expired, migration calls are **rejected explicitly** (no silent fallback to monthly quota).

### Option A — Reusable script (`veilio-migrate.mjs`)

Download from your app (no login required):

```bash
curl -O https://app.veilio.xyz/veilio-migrate.mjs
```

Run on a CSV export:

```bash
export VEILIO_API_KEY="sk_..._MIGRATION_KEY"
export VEILIO_BASE_URL="https://app.veilio.xyz"

node veilio-migrate.mjs \
  --in customers.csv \
  --out customers.tokenized.csv \
  --columns email,phone,full_name \
  --types email=email,phone=phone \
  --entity-id-col customer_id \
  --retention-days 3650 \
  --batch 100 \
  --resume
```

| Flag              | Description                                                          |
| ----------------- | -------------------------------------------------------------------- |
| `--columns`       | Comma-separated sensitive columns to tokenize                        |
| `--types`         | Optional `col=type` mapping (`email`, `phone`, …)                    |
| `--entity-id-col` | Business id column kept in clear (for `entityId` / profile grouping) |
| `--resume`        | Resume from last checkpoint after interruption                       |
| `--dry-run`       | Parse only, no API calls                                             |

The script uses `POST /api/tokenize/bulk` with your migration key and backs off on HTTP 429.

### Option B — Dataset upload (dashboard)

1. Create an organization (required for team sharing; optional for solo import).
2. Upload CSV/JSON from **Datasets** with migration mode enabled (`migration=true` on the upload API).
3. Cell count is debited from the migration budget after successful tokenization.

### API behaviour with a migration key

| Endpoint                               | Monthly quota | Migration budget             |
| -------------------------------------- | ------------- | ---------------------------- |
| `POST /tokenize`, `/bulk`, `/format`   | Skipped       | Consumes tokens created      |
| `POST /flows/ingest`                   | Skipped       | Consumes tokens created      |
| `POST /detokenize` (migration key)     | Skipped       | Not consumed (no new tokens) |
| `POST /datasets` with `migration=true` | Skipped       | Consumes tokenized cells     |

Standard keys (`purpose = STANDARD`) always use monthly plan limits.

### Check migration status

Authenticated dashboard or API:

```bash
GET /api/onboarding/migration
```

Response includes `allowance`, `used`, `remaining`, `expiresAt`, and active migration keys (prefix only).

### Requirements

* Active paid plan (**Starter, Pro, or Enterprise**) — migration is **not** available on Freemium.
* Organization **OWNER** or **DPO** to activate.
* Plan your `migrationAllowance` to cover worst-case cell count (non-empty cells in the export).


# Offboarding & Account Exit

Veilio provides a guided offboarding flow so customers can recover all plaintext data and leave without vendor lock-in. Dashboard: Settings → Compliance → « Leave Veilio » (/dashboard/offboarding).

### Why a guided flow?

Account deletion is **irreversible**. The flow ensures you:

1. Understand the consequences.
2. Export all detokenized data.
3. Archive audit logs.
4. Confirm reintegration into your own systems.
5. Only then delete the account.

### Steps (4 required before deletion)

| Step                 | Requirement                                             | Validation                              |
| -------------------- | ------------------------------------------------------- | --------------------------------------- |
| 1. Understand        | Checkbox acknowledgement                                | Client-side                             |
| 2. Export data       | Full GDPR export (JSON or CSV)                          | Server: `hasCompletedExport` + 2FA      |
| 3. Export audit logs | Download JSON or CSV logs                               | Client: `logsDownloaded` after download |
| 4. Confirm recovery  | Three checkboxes (downloaded / verified / reintegrated) | Client-side                             |
| 5. Delete account    | Password confirmation                                   | `DELETE /api/compliance/delete-account` |

Progress is shown as **X/4** required steps. Deletion stays locked until all four are complete.

### Export data (step 2)

```http
POST /api/compliance/export
Content-Type: application/json

{
  "format": "json",
  "code": "123456"
}
```

* Requires authenticated session + **TOTP 2FA** code.
* Job runs asynchronously; email notification when ready.
* Export **detokenizes all your tokens** — quota checks are bypassed for this operation.
* Download via the one-time link from the offboarding UI or compliance settings.

### Export audit logs (step 3)

```http
GET /api/compliance/logs-export?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD&format=json
```

Choose a date range (default: last 30 days). **Mandatory** before account deletion.

### Inventory preview

Before exporting, the UI loads:

```http
GET /api/compliance/offboarding/summary
```

Returns active token count, datasets, API keys, shredded token count, and export status.

### Crypto-shredded data

Tokens that were **crypto-shredded** are permanently unrecoverable and will **not** appear in the export. The UI warns if shredded tokens exist.

### After deletion

All tokens, logs, API keys, datasets, and the subscription are removed. The user is logged out.


# Secure Export Workflow

This page describes the hardened GDPR/export flow implemented in Veilio.

#### Security posture

* Export request requires authenticated session
* 2FA/TOTP code is required
* Export payload is encrypted at rest
* Download link/token is one-time use
* Token expires automatically
* **Full export is quota-free** (all tokens detokenized regardless of plan)

#### Request flow

1. Client requests export with `POST /api/compliance/export` and body:
   * `format`: `json` or `csv`
   * `code`: TOTP code
2. Veilio validates session + 2FA + rate limit.
3. Export job is created and processed asynchronously.
4. User receives a download link/token (and email notification).

#### Important hardening details

* `GET /api/compliance/export?code=...` is disabled for security.
* Export `fileData` is stored encrypted (`enc_v1:` prefix).
* Download route rotates token on first successful download.
* Reusing same download link returns `410 GONE`.

#### Audit log export (offboarding)

Before deleting an account, users must download audit logs:

```http
GET /api/compliance/logs-export?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD&format=json
```

This step is **mandatory** in the guided offboarding flow (`/dashboard/offboarding`).

#### Guided offboarding

See Offboarding & Account Exit for the full 5-step departure checklist (understand → export data → export logs → confirm → delete).

#### Recommended ops settings

* Keep export token expiry short where possible.
* Monitor repeated failed 2FA/export attempts.
* Keep audit logs for export request + download events.
* **Export before shred/delete** — crypto-shredded tokens cannot be recovered.


# Security Best Practices

<figure><img src="https://871091000-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0WqIAiGHfaanrggvU6xD%2Fuploads%2FbXXakYeKw9A05BhgcIk3%2Fimage.png?alt=media&amp;token=4ee8ccbd-52d1-43e3-8aeb-0da7f9e80ca6" alt=""><figcaption></figcaption></figure>

## Security Best Practices

Production security guidelines for integrating Veilio.

***

#### Key management

* Use **one API key per environment** (`dev`, `staging`, `prod`)
* Use **MIGRATION** keys only for historical imports — rotate or revoke after migration completes
* Rotate keys periodically (quarterly recommended)
* Revoke unused or compromised keys immediately from **Dashboard → API Keys**

***

#### Secret handling

* Store keys in a **secrets manager** (AWS Secrets Manager, GCP Secret Manager, Vault) or secure environment variables
* Never hardcode keys in source code or commit them to Git
* Never expose API keys in browser bundles, mobile apps, or client-side JavaScript
* Restrict CI/CD secrets to deployment pipelines only

***

#### Access control

* Call Veilio **from your backend only** — the API key must never reach end-user devices
* Restrict detokenization to authorized services and user roles in your application
* Require a business **`reason`** on every detokenization call (enforced by Veilio audit logs)
* Use **column policies** on datasets to limit which roles can download which fields
* Assign **VIEWER** role only to users who must not tokenize or shred

***

#### Data handling

* **Store tokens by default**, not raw PII
* Detokenize only at the **last possible moment** in a workflow (send email, generate PDF, open support ticket)
* **Never** detokenize on page load to display raw data in the browser
* Avoid writing detokenized values to application logs, APM traces, or error reports
* Mask tokens in UI when full value is not needed (`tok_abc1...`)

***

#### Reliability and abuse protection

* Implement retries with backoff for `429` responses (SDKs do this automatically)
* Add alerts for repeated `401`, `403`, and `5xx` from your integration
* Monitor request volumes by environment and API key
* Use **bulk endpoints** for imports to reduce request count and rate-limit risk

***

#### Retention and erasure

* Set `retention.ttlDays` or `retentionUntil` at tokenization for time-limited data
* Call `POST /tokens/shred` (or `shredToken` / `shred_token`) on GDPR deletion requests
* Document your data retention policy and align TTLs with legal requirements
* Test shredded tokens return HTTP `410` (`TOKEN_SHREDDED`) — your app must handle this gracefully

***

#### Encryption and architecture

| Layer      | Protection                                                              |
| ---------- | ----------------------------------------------------------------------- |
| In transit | TLS 1.2+ (HTTPS only in production)                                     |
| At rest    | AES-256-GCM for token payloads; separate platform and clients databases |
| Search     | HMAC-SHA256 blind index (no plaintext stored for lookup)                |
| Erasure    | Cryptographic shredding — encryption keys destroyed per token           |

Veilio SaaS runs on isolated infrastructure. **On-premise** deployments keep all tokenized data on your servers — Veilio never receives your business data.

***

#### Network (on-premise)

* Terminate TLS at a reverse proxy (nginx, Traefik, Caddy) in front of the Veilio container
* Restrict inbound access to trusted networks or VPN
* Allow outbound HTTPS only if using automatic license refresh or update banner
* Back up `ENCRYPTION_KEY` and PostgreSQL volumes off-server (see Installation guide)

***

#### Compliance and audit

* Enable SIEM integration for centralized audit on Enterprise / on-premise
* Export audit logs periodically via dashboard compliance tools
* Use offboarding workflow for structured account exit and data portability
* Run dump compliance checks before sharing database exports

***

#### Trust and legal

For DPA, subprocessors list, and security questionnaires, contact <support@veilio.xyz> or <https://veilio.xyz/contact>.

{% hint style="info" %}
Questions or suggestions? <https://veilio.xyz/contact>
{% endhint %}


# Sovereign backups

Sovereign Backup exports an encrypted copy of your detokenization vault, your tokens and the keys needed to decrypt them, sealed with a key that only you hold.

It answers the question every CTO asks before signing: *what happens to my data if Veilio disappears?*

**Who is this for?**

* Teams that would lose access to their data if their own database were wiped or ransomed
* Teams that need a documented exit path from Veilio (procurement, due diligence, DORA)
* Anyone who wants to detokenize without depending on Veilio being online

**Pro or Enterprise** plan required (or on-premise deployment).

***

**What problem it solves**

Tokenization splits the data from its meaning: your database holds tokens, Veilio holds the mapping. That split is the security benefit — and the availability risk.

| Scenario                                          | Without Sovereign Backup                                       | With Sovereign Backup                               |
| ------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------- |
| Your database is wiped or encrypted by ransomware | Tokens are gone from your side                                 | Restore tokens and values from your bundle          |
| Veilio is down                                    | Detokenization unavailable until service returns               | Detokenize offline, immediately                     |
| Veilio is compromised                             | Attacker cannot read your bundles (they lack your private key) | Same — plus you keep an independent copy            |
| You leave Veilio                                  | Requires a coordinated migration                               | You already hold a complete, self-describing export |

***

**Security model**

* The key pair is generated **in your browser**. The private key is downloaded to your machine and **never sent to Veilio**.
* Veilio stores only your **public key** and its SHA-256 fingerprint.
* Each bundle is encrypted with a random AES-256-GCM content key, itself wrapped with your public key using **RSA-OAEP / SHA-256**.
* Veilio cannot read a bundle it just produced. A full compromise of Veilio does not expose your backups.
* Destination credentials (S3 secret key, auth header) are encrypted at rest with the Veilio master key and are never returned by the API.

{% hint style="warning" %}
Veilio cannot recover your private key. If you lose it, existing bundles become permanently unreadable. Store it in a password manager or cold storage.
{% endhint %}

***

**Setup (dashboard)**

1. Sign in as the **organization Owner**.
2. Open **Dashboard → Sovereign Backup**.
3. Click **Generate my key**. Your private key (`veilio-recovery-key.pem`) downloads immediately — save it somewhere safe.
4. Verify the displayed fingerprint matches your key file.

You can now use either mode — or both.

**Manual export**

Click **Download a backup** whenever you want. The bundle is generated on demand and downloaded through your browser.

**Automatic backup**

Configure a destination, run **Test**, then flip the toggle:

| Destination               | What you need                                                                                                                                                          |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **S3-compatible storage** | Endpoint, region, bucket, object key, access key ID and secret. Works with AWS S3, Cloudflare R2, Scaleway, MinIO, and Backblaze B2. Enable path-style URLs for MinIO. |
| **HTTPS endpoint**        | Any URL that accepts a `PUT`, with an optional auth header. Works with presigned URLs and internal storage gateways.                                                   |

Each scheduled run **replaces** the previous bundle at the same location. Your backup stays current without your storage growing over time.

| Plan           | Minimum frequency |
| -------------- | ----------------- |
| **Pro**        | Every 24 hours    |
| **Enterprise** | Hourly            |

On Veilio SaaS, scheduled backups run automatically — no external cron to configure.

{% hint style="info" %}
The connectivity **Test** writes a small `.probe` object next to your backup path. It never overwrites the live bundle.
{% endhint %}

***

**Bundle format**

A bundle is a JSON envelope. Its metadata is readable without the private key; the payload is not.

```json
{
  "format": "veilio.sovereign-backup",
  "version": 1,
  "createdAt": "2026-08-12T10:00:00.000Z",
  "organizationId": "org_...",
  "organizationName": "veilio",
  "keyFingerprint": "sha256:...",
  "algorithms": {
    "keyWrap": "RSA-OAEP-256",
    "content": "AES-256-GCM"
  },
  "wrappedKey": "base64...",
  "payload": "base64...",
  "stats": {
    "tokenCount": 12480,
    "userKeyCount": 3,
    "truncated": false
  }
}
```

`truncated: true` means the organization exceeded the per-bundle token cap and the bundle is incomplete. Contact <support@veilio.xyz> if you see this.

***

**Restoring offline**

The restore tool runs with **no database**, **no network**, and **no Veilio account**. That is what makes the escape hatch real.

```bash
# Inspect a bundle without decrypting it
npx tsx scripts/sovereign-restore.ts --bundle ./backup.veiliobak --inspect

# Restore to JSON
npx tsx scripts/sovereign-restore.ts \
  --bundle ./backup.veiliobak \
  --key ./veilio-recovery-key.pem \
  --out ./restored.json

# Restore to CSV
npx tsx scripts/sovereign-restore.ts \
  --bundle ./backup.veiliobak \
  --key ./veilio-recovery-key.pem \
  --format csv \
  --out ./restored.csv
```

The script lives in the Veilio repository at `scripts/sovereign-restore.ts`. It is intentionally self-contained (Node.js + built-in `crypto` only).

Output rows contain `token`, `value`, `type`, `entityId`, and `createdAt` , enough to rebuild your mapping or reload it into another system.

{% hint style="danger" %}
The restored file contains **plaintext sensitive data** and is written with `0600` permissions. Delete it once you have reloaded your data.
{% endhint %}

***

**Rotating your key**

Generating a new key does **not** invalidate old bundles: each bundle stays readable with the private key that was current when it was created. Keep your previous private keys as long as you keep the bundles they seal.

Rotation pauses automatic backups so you re-enable them deliberately, after confirming the new key is safely stored.

***

**Operational notes**

* Only the organization **Owner** can configure Sovereign Backup or trigger an export.
* Manual exports are rate-limited to **5 per hour** and recorded in your audit log as `EXPORT_BY_USER`.
* Every run manual or scheduled appears in the backup history with its status, token count, size, and any error.
* Shredded tokens are excluded from bundles: a Sovereign Backup never resurrects data you deleted for compliance reasons.
* After a plan downgrade below Pro, automatic backups stop; existing bundles remain readable with your private key.

***

{% hint style="info" %}
Questions or suggestions? Contact <support@veilio.xyz>.
{% endhint %}


# Troubleshooting

#### 401 `AUTH_ERROR`

**Causes**

* Missing API key
* Invalid or revoked API key
* Using a dashboard-only route with an API key (e.g. `DELETE /api/tokens/[id]` requires a session — use `POST /api/tokens/shred` from API instead)

**Fix**

* Verify `Authorization: Bearer <key>` or `X-API-Key: <key>`
* Use the correct key for the environment (dev vs prod)
* For programmatic shred: `POST /api/tokens/shred` or `POST /api/tokens/shred-bulk` (session)

***

#### 400 `VALIDATION_ERROR`

**Cause:** missing fields or invalid payload.

**Fix:** validate the body against HTTP Integration or the API Reference.

Common dataset upload messages:

| Message                                    | Fix                                                 |
| ------------------------------------------ | --------------------------------------------------- |
| `Missing file`                             | Send multipart field `file`                         |
| `columnPolicies JSON string required`      | Pass `columnPolicies` as a JSON string in form data |
| `Missing policy for column "..."`          | Add a policy entry for every column                 |
| `Policy references unknown column "..."`   | Remove extra policy keys                            |
| `JSON dataset must be an array of objects` | Fix JSON shape                                      |
| `Too many cells (max N)`                   | Reduce file size or split the import                |
| `File exceeds max size (N bytes)`          | Split file or contact support for limit increase    |

***

#### 403 `PLAN_LIMIT`

**Cause:** monthly plan quota exceeded (tokens created, API requests, API keys, or datasets).

**Fix**

* Check dashboard usage
* Upgrade plan on `/dashboard/billing`
* For historical import: use a **MIGRATION** API key and ensure migration budget is active (Migration Mode)

***

#### 403 `PLAN_LIMIT_REACHED` (organization invite)

**Cause:** member limit for plan (Starter = 1, Pro = 2).

**Fix:** upgrade to Pro or Enterprise, or contact Veilio for custom `maxMembers`.

***

#### 403 `FORBIDDEN` (VIEWER role)

**Cause:** VIEWER role cannot tokenize, shred, or upload datasets.

**Fix:** ask OWNER/DPO for MEMBER role or API key whitelist.

***

#### 408 `TIMEOUT_ERROR`

**Cause:** request exceeded server or client timeout (large bulk or format operations).

**Fix**

* Reduce batch size (`tokenize/bulk`, `detokenize/bulk`)
* Increase client timeout (SDK: `timeout` / `max_retries`)
* Retry with exponential backoff

***

#### 410 `TOKEN_SHREDDED`

**Cause:** the token was cryptographically shredded (manual shred, retention TTL expired, or GDPR erasure).

**Fix**

* Do **not** retry detokenization — the plaintext is permanently destroyed
* Handle gracefully in your app (show "data no longer available")
* Create a new token if the user provides fresh data

SDK handling:

```ts
// JavaScript
import { TokenShreddedError } from "@veilio/sdk";
```

```python
# Python
from veilio_sdk import TokenShreddedError
```

***

#### 413 `PAYLOAD_TOO_LARGE` (datasets)

**Cause:** dataset file exceeds `VEILIO_DATASET_MAX_BYTES` (default **320 MB**).

**Fix:** split the file into multiple uploads or contact support.

***

#### 429 `RATE_LIMIT_ERROR`

**Cause:** too many requests in a short window.

**Fix:** honor `Retry-After`, use exponential backoff, prefer bulk endpoints. The official SDKs retry automatically.

Response headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After`.

***

#### 500 `INTERNAL_ERROR`

**Fix:** retry with backoff; contact <support@veilio.xyz> with timestamp, endpoint, and `event_id` if available from SIEM.

***

#### Migration mode errors

| Message              | Fix                                                                       |
| -------------------- | ------------------------------------------------------------------------- |
| Migration not active | Activate from `/dashboard/onboarding` or `POST /api/onboarding/migration` |
| Budget exhausted     | Increase allowance or contact support                                     |
| Budget expired       | Re-activate migration window                                              |
| Freemium plan        | Migration requires Starter/Pro/Enterprise                                 |

***

#### Dataset access token errors

| Message                                       | Fix                                                                  |
| --------------------------------------------- | -------------------------------------------------------------------- |
| `Dataset not found`                           | Check dataset ID and organization scope                              |
| `Dataset share keys cannot be used to import` | Use a standard API key or dashboard for upload                       |
| Token expired                                 | Generate a new access token (`POST /api/datasets/[id]/access-token`) |

Max TTL for dataset access tokens: **7 days**. Default on upload: **48 hours**.

***

#### On-premise license errors

| Symptom                                         | Fix                                                                      |
| ----------------------------------------------- | ------------------------------------------------------------------------ |
| Instance won't start, license expired > 30 days | Renew license on control plane                                           |
| Read-only mode                                  | License in grace period — renew before 30-day window ends                |
| Auto-refresh fails                              | Check `VEILIO_LICENSE_REFRESH_TOKEN` and outbound HTTPS to control plane |

See Installation guide.

***

#### Stripe / billing (SaaS)

* **Double charge risk:** do not assign manual Enterprise in admin while a Stripe subscription is still active — cancel Stripe first.
* **Org quotas wrong:** ensure the org was created before Stripe checkout so `organizationId` is on the subscription row.

{% hint style="info" %}
If you have any issues or suggestions, reach out at <https://veilio.xyz/contact> or <support@veilio.xyz>.
{% endhint %}


# SIEM Integration

Veilio pushes real-time audit events to your SIEM or security stack. Veilio does not replace your SIEM — it feeds it.

#### Who is this for?

* Security teams (CISO, SOC) monitoring tokenization and detokenization
* Compliance / DPO teams needing centralized audit trails
* Enterprise customers on Splunk, Microsoft Sentinel, QRadar, Elastic, or custom collectors

Enterprise plan required (or on-premise deployment).

***

#### Setup (dashboard)

1. Sign in as the **organization Owner**.
2. Open **Dashboard → Webhooks**.
3. Choose **Generic webhook** or **Splunk HEC**.
4. Enter your HTTPS collector URL.
5. Select **event filters** (which `event_type` values to forward).
6. **Create** — copy the signing secret (generic webhook only; shown once).
7. Click **Test** to send a `SIEM_TEST` event.

***

#### Event payload

```json
{
  "event_id": "evt_...",
  "timestamp": "2026-07-03T10:00:00.000Z",
  "source": "veilio",
  "event_type": "DETOKENIZE",
  "severity": "high",
  "organization_id": "org_...",
  "actor": {
    "user_id": "...",
    "api_key_id": "...",
    "ip_address": "...",
    "user_agent": "..."
  },
  "resource": {
    "token_id": "tok_...",
    "token_type": "email"
  },
  "outcome": "success",
  "reason": "Support ticket resolution",
  "error": null
}
```

No plaintext PII is ever included in SIEM payloads.

***

#### Event type catalogue

Configure which events to forward per destination. All types below are available as dashboard filters.

| `event_type`     | Severity | When emitted                           |
| ---------------- | -------- | -------------------------------------- |
| `TOKENIZE`       | medium   | A new token is created                 |
| `DETOKENIZE`     | **high** | Plaintext is revealed via detokenize   |
| `SHRED`          | **high** | A token is cryptographically destroyed |
| `API_KEY_CREATE` | **high** | A new API key is created               |
| `API_KEY_DELETE` | **high** | An API key is revoked or deleted       |
| `USER_CREATE`    | medium   | A new user account is created          |
| `USER_UPDATE`    | medium   | User profile or settings updated       |
| `USER_LOGIN`     | medium   | Successful dashboard login             |
| `SIEM_TEST`      | low      | Manual test from Webhooks dashboard    |

**Recommended filters for SOC**

| Use case                     | Filter                             |
| ---------------------------- | ---------------------------------- |
| Data exfiltration monitoring | `DETOKENIZE`, `SHRED`              |
| Credential / key hygiene     | `API_KEY_CREATE`, `API_KEY_DELETE` |
| Access anomalies             | `USER_LOGIN`, `DETOKENIZE`         |
| Full audit trail             | All event types                    |

***

#### Signature verification (generic webhook)

**Header:** `X-Veilio-Signature: t=<unix_timestamp>,v1=<hmac_sha256_hex>`

**Signed string:** `<unix_timestamp>.<raw_json_body>`

**Verification steps**

1. Parse `t` and `v1` from the header.
2. Reject if `t` is older than 5 minutes (replay protection).
3. Compute `HMAC-SHA256(secret, t + "." + rawBody)` as hex.
4. Compare with `v1` using a constant-time comparison.

**Node.js example**

```js
import crypto from "crypto";

function verifyVeilioSignature(secret, rawBody, header) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.trim().split("="))
  );
  const t = parts.t;
  const v1 = parts.v1;
  if (!t || !v1) return false;
  if (Date.now() / 1000 - Number(t) > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
```

***

#### Splunk HEC

* **URL:** `https://<host>:8088/services/collector/event`
* Provide your HEC token in the Veilio form.
* Optional: Splunk index and sourcetype (`_json` by default).
* **Authorization header:** `Splunk <token>`

***

#### Common SIEM mappings

| Platform                 | Approach                                                                           |
| ------------------------ | ---------------------------------------------------------------------------------- |
| **Splunk**               | Use **Splunk HEC** destination type in Veilio                                      |
| **Microsoft Sentinel**   | Logic App or Azure Function behind generic webhook → map `event_type` to incidents |
| **QRadar**               | Custom HTTPS log source → parse JSON, use `event_type` as category                 |
| **Elastic / OpenSearch** | Ingest pipeline on generic webhook; index by `organization_id` + `event_type`      |
| **Palo Alto XSOAR**      | Webhook integration → incident mapping on `DETOKENIZE` + `SHRED`                   |

**Sentinel quick pattern**

1. Create a Logic App with **When a HTTP request is received**.
2. Paste the URL into Veilio generic webhook.
3. Filter on `event_type == "DETOKENIZE"` for high-severity alerts.
4. Forward to Sentinel via the built-in connector.

***

#### Reliability and delivery log

* Async delivery with **3 retries** (0s, 2s, 8s backoff)
* **10s** HTTP timeout per attempt
* Delivery status visible in **Dashboard → Webhooks** (per destination)
* Failed deliveries include `outcome: "failure"` and `error` in the payload when applicable

**Idempotency:** use `event_id` as a deduplication key in your SIEM — Veilio generates a unique ID per event.

***

#### Related links

* Security Best Practices
* Troubleshooting


# Installation guide

This guide covers the full path from purchasing an on-premise license on the Veilio SaaS to running a self-hosted instance with Docker and a license.jwt file.

> **Two separate planes**
>
> * **Control plane** = the Veilio SaaS ([app.veilio.xyz](https://app.veilio.xyz)). You sign up here, pay via Stripe, and download your license and deployment kit. Veilio never sees your tokenized data.
> * **Your on-premise instance** = what you deploy on your own servers. It contains only the public key and validates the license offline. No business data leaves your infrastructure.

***

**1. Prerequisites (your server)**

Before deploying on your infrastructure, make sure you have:

* Docker ≥ 24 and Docker Compose ≥ 2.20
* 2 CPUs, 2 GB RAM minimum
* A Linux host (or VM) with outbound HTTPS access only if you want automatic license renewal or the in-app update banner (optional)
* No Docker registry login — the application image is public on [Docker Hub (`veilio/app:latest`)](https://hub.docker.com/r/veilio/app)

You will obtain `license.jwt` and the deployment kit from the Veilio SaaS — see section 2.

***

**2. Purchase workflow (Veilio SaaS)**

Everything in this section happens on the **control plane** (the Veilio web app), not on your on-premise server.

**2.1 Create your account**

1. Open [app.veilio.xyz](https://app.veilio.xyz) and click **Sign up**.
2. Register with your work email and set a password.
3. Confirm your email if prompted, then sign in.

You do not need a cloud (SaaS) subscription to buy an on-premise license. On-premise is a separate product, purchased from **Billing**.

**2.2 Buy an on-premise license**

1. Go to **Dashboard → Billing** (`/dashboard/billing`).
2. Scroll to the **Veilio On-Premise** block.
3. Choose an annual license:

| Plan                | Price (excl. tax) | Best for                                               |
| ------------------- | ----------------- | ------------------------------------------------------ |
| On-Premise Standard | €1,690 / year     | Teams with annual quotas (users, tokens, API requests) |
| On-Premise Pro      | €2,990 / year     | High-volume deployments with no token or request caps  |

4. Click **Start 1-month free trial** on the plan you want.
5. Enter your **company name** (embedded in the signed license).
6. Complete Stripe checkout. A card is required, but you are **not charged** during the 1-month trial.

**2.3 What you receive after purchase**

| Source           | Content                                                                          |
| ---------------- | -------------------------------------------------------------------------------- |
| Email            | `license.jwt` attached to the delivery email                                     |
| Email (optional) | `vlr_...` refresh token for automatic license renewal (shown **once** — save it) |
| Billing page     | **Download deployment kit** and **Download my license** buttons                  |

The first delivery is a **trial license** (30 days). When the trial ends and Stripe confirms payment, you receive a full annual license by email.

**2.4 Download the deployment kit and license**

1. **Download deployment kit** → `veilio-onpremise-kit.zip` containing `docker-compose.yml`, `.env.onpremise.example`, `README.md`
2. **Download my license** → `license.jwt`

> `license.jwt` is **not** inside the zip. Place it in the `veilio/` folder next to `docker-compose.yml` on your server.

**2.5 Prepare files on your server**

```bash
unzip veilio-onpremise-kit.zip && cd veilio-onpremise
mkdir -p veilio
cp ~/Downloads/license.jwt veilio/license.jwt
```

***

**3. Configure the environment**

```bash
cp .env.onpremise.example .env
```

Edit `.env` and set at least:

| Variable                                 | Description                                                                                                                |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `DB_PASSWORD`                            | Strong PostgreSQL password                                                                                                 |
| `ENCRYPTION_KEY`                         | `openssl rand -base64 32` — **back it up: without it, tokens cannot be recovered**                                         |
| `NEXTAUTH_SECRET`                        | `openssl rand -base64 32`                                                                                                  |
| `APP_URL`                                | Public URL (e.g. `https://veilio.acme.internal`)                                                                           |
| `VEILIO_IMAGE`                           | Docker image (default: `veilio/app:latest`)                                                                                |
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_FROM`  | Outbound email                                                                                                             |
| `BOOTSTRAP_EMAIL` / `BOOTSTRAP_PASSWORD` | First admin on empty database                                                                                              |
| `VEILIO_LICENSE_FILE`                    | Default: `/veilio/license.jwt` (file mode)                                                                                 |
| `VEILIO_LICENSE_KEY`                     | Full JWT inline (Coolify / secret managers) — overrides file                                                               |
| `VEILIO_CRON_SECRET`                     | Bearer secret for internal jobs (auto-shred). Required if you use retention / TTL. Generate with `openssl rand -base64 32` |

**Automatic renewal (optional):**

| Variable                                | Description                   |
| --------------------------------------- | ----------------------------- |
| `VEILIO_LICENSE_AUTO_REFRESH`           | `true` to enable              |
| `VEILIO_LICENSE_CONTROL_PLANE_URL`      | `https://app.veilio.xyz`      |
| `VEILIO_LICENSE_REFRESH_TOKEN`          | `vlr_...` from delivery email |
| `VEILIO_LICENSE_REFRESH_THRESHOLD_DAYS` | Default `45`                  |

***

**4. Configure the license**

**Option A — File (default)**

```bash
VEILIO_LICENSE_FILE=/veilio/license.jwt
mkdir -p veilio && cp ~/Downloads/license.jwt veilio/license.jwt
```

For auto-refresh persistence, mount `./veilio` read-write (remove `:ro` in `docker-compose.yml`).

**Option B — Environment variable (Coolify, PaaS)**

```bash
VEILIO_LICENSE_KEY=eyJ...full_jwt_content...
# do not set VEILIO_LICENSE_FILE
```

***

**5. Start**

```bash
docker compose up -d
```

On startup the instance: applies migrations, verifies the license, creates bootstrap admin if configured, and optionally refreshes the license.

***

**6. Check status**

```bash
curl -s http://localhost:3000/api/health?db=1 | jq
```

For license details, send header `X-Metrics-Secret` with your `METRICS_SECRET` value.

**SDK / API base URL for your instance:** `https://your-domain.com/api`

***

**7. Retention & auto-shred (required if you use TTL)**

If your apps pass `retention.ttlDays` or `retention.retentionUntil` when tokenizing, you must schedule the auto-shred job yourself. Setting retention alone does **not** destroy data.

1. Set `VEILIO_CRON_SECRET` in `.env` and restart the app if needed.
2. Schedule an hourly call (crontab, systemd timer, or your orchestrator):

```bash
curl -fsS -X POST "https://your-domain.com/api/internal/tokens/auto-shred?limit=500" \
  -H "Authorization: Bearer $VEILIO_CRON_SECRET"
```

3. Confirm a `200` JSON response with a `processed` field.

Full details: [Data Lifecycle: Retention and Crypto Shredding](https://docs.veilio.xyz/documentation/basics/data-lifecycle).

***

**8. Update the application**

```bash
docker compose pull && docker compose up -d
```

PostgreSQL volumes and `license.jwt` are preserved.

***

**9. Create an admin manually (alternative)**

```bash
docker compose exec veilio-app pnpm onpremise:seed-admin -- \
  --email admin@acme.internal --password 'ChangeMe123!' --company "Acme"
```

***

**10. Coolify deployment**

1. **New Resource** → **Docker Compose** (image on Docker Hub, do not build from Git).
2. Deploy the kit's `docker-compose.yml`.
3. Set environment variables (section 3), including `VEILIO_CRON_SECRET` if you use retention.
4. Set `VEILIO_LICENSE_KEY` as a secret (option B, section 4).
5. Assign domain to `veilio-app`, port **3000**.
6. `APP_URL` = Coolify HTTPS URL.
7. Configure an external cron / Coolify scheduled job for auto-shred (section 7).

Verify: `curl -s https://your-domain/api/health?db=1`

***

**License lifecycle**

| State                 | Behavior                                 |
| --------------------- | ---------------------------------------- |
| Valid                 | Everything works                         |
| 30 days before expiry | Warning banner + `health: expiring_soon` |
| Expired (0–30 days)   | Grace period: read-only, no new tokens   |
| Expired > 30 days     | Instance refuses to start                |

**Automatic renewal** — add to `.env`:

```bash
VEILIO_LICENSE_AUTO_REFRESH=true
VEILIO_LICENSE_CONTROL_PLANE_URL=https://app.veilio.xyz
VEILIO_LICENSE_REFRESH_TOKEN=vlr_...
```

**Manual swap (air-gap):**

```bash
cp ~/Downloads/license.jwt veilio/license.jwt
docker compose restart veilio-app
```

Re-download anytime from **Dashboard → Billing → Download my license**.

***

**Backups**

You must back up:

* Both PostgreSQL volumes (`veilio-data`, `clients-data`)
* The `ENCRYPTION_KEY` variable (off-server, in a secrets vault)
* `VEILIO_CRON_SECRET` (same vault — rotating it without updating the cron job breaks auto-shred)

***

**TLS / reverse proxy (recommended)**

Terminate HTTPS in front of the container:

```nginx
# nginx example
location / {
  proxy_pass http://127.0.0.1:3000;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Proto https;
}
```

Set `APP_URL` to your public HTTPS URL.


# Go-live Checklist

Use this checklist before production rollout.

#### Environment and secrets

* [ ] Separate API keys for dev/staging/prod
* [ ] Standard keys for runtime; **MIGRATION** keys only for historical import
* [ ] All keys stored in secure environment variables or secret manager
* [ ] No secrets committed to code repository

#### Integration quality

* [ ] Tokenize on write paths validated
* [ ] `entityId` set for profile grouping where needed
* [ ] Detokenize only in approved workflows
* [ ] `reason` provided for detokenization calls
* [ ] Bulk endpoints used for high-volume operations

#### Legacy data migration

* [ ] Migration mode activated and budget sized for export cell count
* [ ] `veilio-migrate.mjs` tested on sample CSV with `--dry-run`
* [ ] Checkpoint/resume tested (`--resume`) for large files
* [ ] Migration key rotated or revoked after import completes

#### Organizations (if team sharing)

* [ ] Organization created before owner Stripe subscription (Pro+ for 2+ people)
* [ ] Column policies defined per dataset
* [ ] VIEWER whitelist reviewed if using invite-only read access

#### Reliability

* [ ] Retry strategy implemented for `429` and transient `5xx`
* [ ] Monitoring and alerts on error rates
* [ ] Integration load tests completed

#### Security and operations

* [ ] API key rotation process documented
* [ ] Incident response runbook created
* [ ] Access permissions reviewed (least privilege)
* [ ] Shred strategy defined (per-token, bulk, `entityId` profile)
* [ ] Dataset public share link TTL policy reviewed
* [ ] Offboarding path documented (export data + logs before delete)

#### Business readiness

* [ ] Support team trained on token-based workflows
* [ ] Compliance stakeholders validated process
* [ ] Rollback plan documented
* [ ] Billing model confirmed (Stripe self-service vs Enterprise invoice)

{% hint style="info" %}
Make sure to validate all your tests before moving to production.
{% endhint %}


# Operations: Dump Compliance

This page is the practical runbook for validating SQL dumps against plaintext PII leakage.

Validate SQL database dumps before sharing, restoring, or promoting them to production. This runbook helps ensure no plaintext PII leaked into a dump that should only contain Veilio tokens (`tok_...`).

***

#### What this checks

The compliance scanner scans dump text for patterns that indicate **plaintext sensitive data**:

| Rule ID              | Detects                            |
| -------------------- | ---------------------------------- |
| `email-plaintext`    | Email addresses                    |
| `phone-e164-fr-like` | Phone numbers (E.164 / FR formats) |
| `iban-like`          | IBAN-like values                   |
| `credit-card-like`   | Card numbers (13–19 digits)        |
| `ssn-like`           | US SSN format (`###-##-####`)      |

**Excluded by default:** Veilio tokens matching `tok_[A-Za-z0-9_-]{10,}`.

Any match above the allowed threshold → **FAIL**.

***

#### Quick start (standalone script)

No Veilio repository access required. You need **Node.js 18+**.

**1) Save the policy file**

Create `dump-policy.json`:

```json
{
  "description": "Dump compliance policy. Any match above maxAllowed fails the check.",
  "maxFindingsPreview": 20,
  "rules": [
    {
      "id": "email-plaintext",
      "description": "Detect plaintext email addresses",
      "regex": "\\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\\b",
      "maxAllowed": 0
    },
    {
      "id": "phone-e164-fr-like",
      "description": "Detect phone numbers likely in E.164/FR formats",
      "regex": "(\\+\\d{6,15}|\\b0[1-9](?:[ .-]?\\d{2}){4}\\b)",
      "maxAllowed": 0
    },
    {
      "id": "iban-like",
      "description": "Detect IBAN-like values",
      "regex": "\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b",
      "maxAllowed": 0
    },
    {
      "id": "credit-card-like",
      "description": "Detect possible card numbers",
      "regex": "\\b(?:\\d[ -]*?){13,19}\\b",
      "maxAllowed": 0
    },
    {
      "id": "ssn-like",
      "description": "Detect SSN-like US format",
      "regex": "\\b\\d{3}-\\d{2}-\\d{4}\\b",
      "maxAllowed": 0
    }
  ],
  "exclusions": [
    {
      "id": "token-prefix",
      "description": "Ignore Veilio tokenized values",
      "regex": "\\btok_[A-Za-z0-9_-]{10,}\\b"
    }
  ]
}
```

**2) Save the checker script**

Create `check-dump-pii.mjs`:

```js
#!/usr/bin/env node
import fs from "fs";

const [dumpPath, policyPath = "dump-policy.json"] = process.argv.slice(2);
if (!dumpPath) {
  console.error("Usage: node check-dump-pii.mjs <dump.sql> [dump-policy.json]");
  process.exit(1);
}

const policy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
const text = fs.readFileSync(dumpPath, "utf8");

function stripExclusions(input, exclusions) {
  let out = input;
  for (const ex of exclusions ?? []) {
    out = out.replace(new RegExp(ex.regex, "g"), "");
  }
  return out;
}

const scrubbed = stripExclusions(text, policy.exclusions);
const findings = [];

for (const rule of policy.rules ?? []) {
  const re = new RegExp(rule.regex, "g");
  const matches = [...scrubbed.matchAll(re)];
  const count = matches.length;
  const maxAllowed = rule.maxAllowed ?? 0;
  if (count > maxAllowed) {
    findings.push({
      id: rule.id,
      description: rule.description,
      count,
      maxAllowed,
      samples: matches.slice(0, 5).map((m) => m[0]),
    });
  }
}

if (findings.length > 0) {
  console.error("FAIL — plaintext PII detected:");
  console.error(JSON.stringify(findings, null, 2));
  process.exit(2);
}

console.log("PASS — no disallowed plaintext findings");
process.exit(0);
```

**3) Run the check**

```bash
node check-dump-pii.mjs /path/to/dump.sql dump-policy.json
```

#### Exit codes

| Code | Meaning                                       |
| ---- | --------------------------------------------- |
| `0`  | PASS — no disallowed plaintext findings       |
| `2`  | FAIL — policy violation (plaintext PII found) |
| `1`  | Script / config / runtime error               |

***

#### Suggested CI gate

1. Generate dump artifact (e.g. `pg_dump`).
2. Run `node check-dump-pii.mjs dump.sql`.
3. Block pipeline on non-zero exit code.

**GitHub Actions example**

```yaml
- name: Check dump for plaintext PII
  run: node check-dump-pii.mjs artifacts/db-dump.sql dump-policy.json
```

***

#### Incident handling

If the checker fails:

1. **Stop** dump promotion or import immediately.
2. Identify the **table / column** source of plaintext values.
3. Verify tokenization is enforced at the ingestion path (write path must call `/tokenize` before DB insert).
4. Re-run dump generation and checker after fix.

***

#### Customizing the policy

* Add rules for national ID formats, passport numbers, or internal identifiers.
* Adjust `maxAllowed` for known false positives (e.g. test data patterns).
* Add `exclusions` regexes for hashed values or known-safe placeholders.

Contact <support@veilio.xyz> if you need help tuning policies for your schema.


# Suggestions & Contact

We want your integration experience to be fast and smooth.

### Share a suggestion

If you have an idea to improve Veilio docs, SDKs, or API experience, please share:

* Your use case
* What blocked you (if anything)
* What would have made integration easier

### Report an issue

When reporting an issue, include:

* Endpoint or SDK method used
* Error code and message
* Approximate timestamp
* Environment (`dev`, `staging`, `prod`)
* Steps to reproduce

This helps us resolve issues faster.

### Contact channels

* Product and integration questions: `support@veilio.xyz`
* Sales and partnership requests: `support@veilio.xyz`

If your team is evaluating Veilio, we can help you scope a quick test use case (profile page, contact form, or CSV batch) and define a practical rollout path.

### Related links

* Product website: <https://veilio.xyz>
* Contact / Demo: <https://veilio.xyz/contact>


# Developer Platform API

***

#### Quick reference

**Base URL**

| Environment       | URL                          |
| ----------------- | ---------------------------- |
| Production (SaaS) | `https://app.veilio.xyz/api` |
| On-premise        | `https://<your-domain>/api`  |
| Local / dev       | `http://localhost:3000/api`  |

> Use `https://app.veilio.xyz/api`, not `api.veilio.com`.

**Authentication**

| Method                | Header                                                      |
| --------------------- | ----------------------------------------------------------- |
| API key (recommended) | `Authorization: Bearer <API_KEY>` or `X-API-Key: <API_KEY>` |
| Dashboard session     | Cookie `veilio_session`                                     |
| Dataset download      | `x-veilio-dataset-access-token: dsat_...`                   |
| Public dataset link   | Query `?public_token=...` (one-time)                        |

**API key purposes**

| Purpose     | Use                                                          |
| ----------- | ------------------------------------------------------------ |
| `STANDARD`  | Production tokenize / detokenize (monthly quota)             |
| `MIGRATION` | Historical import only (migration budget, not monthly quota) |

***

#### Plan quotas (SaaS)

| Plan       | Tokens / month | Requests / month | API keys | Datasets  | Members   | Bulk | Multi-format |
| ---------- | -------------- | ---------------- | -------- | --------- | --------- | ---- | ------------ |
| Freemium   | 500            | 1 000            | 1        | 1         | 1         | No   | No           |
| Starter    | 2 000          | 10 000           | 2        | 4         | 1         | Yes  | Yes          |
| Pro        | 10 000         | 100 000          | 5        | 10        | 2         | Yes  | Yes          |
| Enterprise | Custom         | Custom           | Custom   | Unlimited | Unlimited | Yes  | Yes          |

A **request** counts successful `TOKENIZE`, `DETOKENIZE`, `SHRED`, and `DATASET_UPLOAD` operations. **Tokens** count new tokens created in the clients database.

On-premise limits are defined in your signed `license.jwt`.

***

#### Core endpoints (API key)

| Method | Path                 | Description                |
| ------ | -------------------- | -------------------------- |
| POST   | `/tokenize`          | Single field               |
| POST   | `/tokenize/bulk`     | Batch fields               |
| POST   | `/tokenize/format`   | JSON / CSV / SQL in-place  |
| POST   | `/detokenize`        | Reveal one token           |
| POST   | `/detokenize/bulk`   | Reveal many                |
| POST   | `/detokenize/format` | Multi-format reveal        |
| POST   | `/tokens/shred`      | Crypto-shred one token     |
| POST   | `/flows/ingest`      | Form / lead JSON ingestion |

Detailed curl examples: HTTP Integration.

**POST `/tokenize`**

```json
// Request
{
  "data": "john@example.com",
  "type": "email",
  "metadata": { "entityId": "customer_42" },
  "retention": { "ttlDays": 365 }
}

// Response 200
{
  "token": "tok_...",
  "createdAt": "2026-03-26T10:00:00.000Z",
  "retentionUntil": "2027-03-26T10:00:00.000Z"
}
```

**POST `/tokenize/bulk`**

```json
// Request
{
  "fields": [
    { "data": "a@example.com", "type": "email" },
    { "data": "+336...", "type": "phone", "retention": { "ttlDays": 30 } }
  ]
}

// Response 200
{
  "tokens": [{ "token": "tok_...", "type": "email", "createdAt": "...", "retentionUntil": null }],
  "summary": { "total": 2, "success": 2, "failed": 0 },
  "errors": [{ "field": 1, "error": "..." }],
  "createdAt": "..."
}
```

**POST `/flows/ingest`**

```json
// Request
{
  "flowId": "signup-v1",
  "source": "website",
  "entityId": "lead_42",
  "data": { "email": "john@example.com", "phone": "+336..." },
  "metadata": {}
}

// Response 201
{
  "flowId": "signup-v1",
  "protectedData": { "email": "tok_...", "phone": "tok_...", "company": "Acme" },
  "tokens": [{ "path": "email", "token": "tok_...", "type": "email" }],
  "lookup": { "email_hash": "...", "phone_hash": "..." },
  "policyApplied": "api_key_schema",
  "metadata": {}
}
```

***

#### Dashboard / session endpoints

Require an authenticated dashboard session (not an API key unless noted).

| Method | Path                              | Description                                                |
| ------ | --------------------------------- | ---------------------------------------------------------- |
| GET    | `/tokens`                         | Paginated token list (RBAC)                                |
| POST   | `/tokens/lookup`                  | Search by plaintext value or `entityId` (**session only**) |
| POST   | `/tokens/reveal-bulk`             | Reveal up to 100 tokens                                    |
| POST   | `/tokens/shred-bulk`              | Shred by `ids[]` or `entityId`                             |
| POST   | `/compliance/export`              | Start GDPR export (2FA)                                    |
| GET    | `/compliance/logs-export`         | Download audit logs                                        |
| GET    | `/compliance/offboarding/summary` | Offboarding inventory                                      |
| DELETE | `/compliance/delete-account`      | Delete account                                             |
| POST   | `/onboarding/migration`           | Activate migration mode                                    |
| GET    | `/onboarding/migration`           | Migration budget status                                    |

> **Note:** `POST /tokens/lookup` is not available with an API key. For backend integrations, store `entityId` in token `metadata` at tokenization time and query your own database by token ID.

***

#### Datasets (hybrid auth)

| Method | Path                          | Auth                           | Description                     |
| ------ | ----------------------------- | ------------------------------ | ------------------------------- |
| GET    | `/datasets`                   | API key or session             | List datasets                   |
| POST   | `/datasets`                   | API key or session (Owner/DPO) | Upload CSV or JSON              |
| GET    | `/datasets/[id]`              | API key or session             | Dataset metadata                |
| GET    | `/datasets/[id]/download`     | API key + dataset access token | Filtered detokenized export     |
| POST   | `/datasets/[id]/access-token` | Session                        | Issue short-lived `dsat_` token |
| POST   | `/datasets/[id]/public-share` | Session                        | One-time public link            |

Upload details: Datasets Upload.

**Default limits**

| Variable                   | Default                    |
| -------------------------- | -------------------------- |
| `VEILIO_DATASET_MAX_BYTES` | 320 MB (335 544 320 bytes) |
| `VEILIO_DATASET_MAX_CELLS` | 200 000 cells              |

***

#### Common errors

| HTTP | Code                | Meaning                           |
| ---- | ------------------- | --------------------------------- |
| 401  | `AUTH_ERROR`        | Missing / invalid key or session  |
| 400  | `VALIDATION_ERROR`  | Bad payload                       |
| 403  | `PLAN_LIMIT`        | Quota exceeded                    |
| 403  | `FORBIDDEN`         | RBAC (e.g. VIEWER write)          |
| 408  | `TIMEOUT_ERROR`     | Request timeout                   |
| 410  | `TOKEN_SHREDDED`    | Token cryptographically destroyed |
| 413  | `PAYLOAD_TOO_LARGE` | Dataset file too large            |
| 429  | `RATE_LIMIT_ERROR`  | Rate limit exceeded               |
| 500  | `INTERNAL_ERROR`    | Server error                      |

Full troubleshooting: Troubleshooting.

***

#### OpenAPI specification

A machine-readable OpenAPI 3.0 file is available in the Veilio documentation repository:

`docs/gitbook/openapi.yaml`

Import it into Postman, Insomnia, or your API gateway for interactive testing.

***

#### SDKs

| Language                | Package                                                           |
| ----------------------- | ----------------------------------------------------------------- |
| JavaScript / TypeScript | `@veilio/sdk` on [npm](https://www.npmjs.com/package/@veilio/sdk) |
| Python                  | `veilio-sdk` on [PyPI](https://pypi.org/project/veilio-sdk/)      |

Docs: SDK JS · SDK Python


# Model Context Protocol (MCP)

Veilio integration guide for AI assistants, powered by [KBrain](https://www.kbrain.io).

### Overview

The Model Context Protocol (MCP) lets AI assistants (Cursor, Claude Desktop, and others) connect to external knowledge sources while they help you.

Veilio provides an MCP server that exposes our integration guide. Instead of pasting documentation into every chat, you connect this server once and your assistant queries the Veilio knowledge base directly, for tokenization, the API, SDKs, compliance, and more.

**Who is this for?**

* Developers integrating Veilio into an existing application
* CTOs, DPOs, CISOs, and product teams who want to understand Veilio's security model
* Anyone building with AI coding assistants who wants accurate Veilio answers

### What the Veilio MCP provides

Once connected, your assistant can use the search\_knowledge tool to query the Veilio Guide brain before answering.

The guide covers:

* What Veilio is and how it protects sensitive data (tokenization, encryption, access control)
* Step-by-step integration workflows
* Audit logging, crypto-shredding, and secure data sharing

**Note:** This MCP complements static documentation and SDKs. It does not replace an API key or dashboard access.

### Configuration

#### Prerequisites

* An MCP-compatible client: Cursor (v0.42+), Claude Desktop, or any tool that supports MCP over HTTP
* No authentication required (public endpoint)

#### Server URL

`https://www.kbrain.io/api/mcp/open/kb-veilio-guide-mqj07vow`

#### Cursor

1. Open Settings, then MCP, then Add new global MCP server (or edit your mcp.json file).
2. Add this configuration:

```json
{
  "mcpServers": {
    "veilio-guide": {
      "url": "https://www.kbrain.io/api/mcp/open/kb-veilio-guide-mqj07vow",
      "transport": "streamable-http"
    }
  }
}
```

3. Restart Cursor if needed. A green indicator confirms the connection.

Global config path: `~/.cursor/mcp.json`\
Project config path: `.cursor/mcp.json` at the repository root.

#### Claude Desktop

Edit your Claude Desktop config file and add the same JSON block as above.

macOS path: `~/Library/Application Support/Claude/claude_desktop_config.json`

### Example prompts

Once the MCP is connected, you can ask:

* How do I tokenize an email with the JavaScript SDK?
* What is the difference between tokenize and tokenizeBulk?
* How do I configure a retention policy and crypto-shredding?
* Which endpoints should I use to upload a CSV dataset?

The assistant queries the Veilio knowledge base before answering, which reduces hallucinations about the API.

### About KBrain

This MCP is hosted by our partner [KBrain](https://www.kbrain.io). KBrain turns a knowledge base (guides, specs, FAQs) into an MCP endpoint usable by any compatible assistant. It allows experts, creators, and companies to package their knowledge, documentation, and best practices into reusable AI-ready Brains that can be used directly by ChatGPT, Claude, and AI agents.\
\
Instead of rewriting the same prompts and context repeatedly, users create a Brain once and make their expertise - including technical documentation and user guides - instantly accessible everywhere.

For classic documentation (API reference, SDKs, step-by-step guides), continue using [docs.veilio.xyz](https://docs.veilio.xyz). The MCP is an accelerator for AI-assisted integration.

### Support

Technical questions: <support@veilio.xyz>


