> ## Documentation Index
> Fetch the complete documentation index at: https://docs.databunker.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Store user fields

> Choose between storing a whole user profile and tokenizing individual fields, and share a single field without handing out a credential.

Databunker Pro can hold a user's data two ways: as **one encrypted profile** addressed by a single token, or as **individual tokenized values**, one token per field. They are not interchangeable — the choice decides which features you keep and how much the data costs to store.

This guide shows both, then a third thing people often reach for when they say "per-field": exposing one field of a profile to an outside party without giving that party a credential.

## Option 1 — Store the whole profile

`UserCreate` encrypts the entire profile as one record and returns a UUID token.

```bash theme={null}
curl -X POST https://your-databunker/v2/UserCreate \
  -H "X-Bunker-Token: YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "profile": {
      "email": "jane@example.com",
      "phone": "+12065550100",
      "login": "jane",
      "custom": "legacy-4471",
      "first": "Jane",
      "last": "Doe",
      "address": "1 Market Street, San Francisco, CA",
      "dob": "1985-04-12",
      "creditcard": "4532015112830366",
      "ssn": "123-45-6789"
    }
  }'
```

```json theme={null}
{ "status": "ok", "token": "8ba70d19-a8b1-0b82-f0b9-d5952221be67" }
```

Store that token in your own database in place of every field above. Read the record back by the token or by any indexed field — `email`, `phone`, `login`, `custom`:

```bash theme={null}
curl -X POST https://your-databunker/v2/UserGet \
  -H "X-Bunker-Token: YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"email","identity":"jane@example.com"}'
```

Use `UserCreateBulk` to load many profiles in one call — see [Migrating a SQL users table](/pro/migrations/sql-users-table).

### What you get

* **Indexed lookup** by `email`, `phone`, `login` and `custom`, using secure hashed indexes.
* **App data** for fields that do not belong in the profile. `AppdataCreate` attaches a named JSON record to the user — `"appname": "billing"`, `"appname": "preferences"` — kept separate from the profile and readable on its own with `AppdataGet`. A user can have as many as you need.
* **Group membership.** `GroupAddUser` puts the user in a group, so you can organise users and scope [access-control policies](/pro/administration/access-control) to a group rather than to individuals.
* **One licensed record** per user, however many fields it holds. App data, consents, sessions and the full version history are all included in that one record, and a card stored inside the profile is not metered separately.
* **Erasure in one call.** `UserDelete` wipes the profile; the token remains as an empty shell, lookups by email stop resolving, and anything derived from the profile stops returning data. `UserDeleteBulk` does the same for many users at once.
* Everything else keyed to a user token: [shared records](/pro/concepts/shared-records), [record versioning](/pro/concepts/record-versioning), consent agreements, data-subject requests, and a per-user audit trail.

### What it costs you

* **The four indexed fields must be unique.** `email`, `phone`, `login` and `custom` are what makes lookup possible, and no two users can share a value in any of them — a second `UserCreate` with an email already in use fails with `Duplicate index: email`. Two family members sharing one phone number cannot both carry it in `phone`; keep the shared value in an ordinary field, which has no such constraint.
* The record is **all or nothing**. Anyone who can call `UserGet` sees every field, unless you restrict them with an [access-control policy](/pro/administration/access-control).

## Option 2 — Tokenize individual fields

`TokenCreate` tokenizes a single value. It has no connection to any user record. What comes back depends on the `tokentype`:

<Tabs>
  <Tab title="A string — user name">
    ```bash theme={null}
    curl -X POST https://your-databunker/v2/TokenCreate \
      -H "X-Bunker-Token: YOUR_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "tokentype": "string",
        "record": "Jane Doe",
        "unique": true
      }'
    ```

    ```json theme={null}
    {
      "status": "ok",
      "tokenuuid": "a07d6340-2b45-ee79-98a5-e5c737966b3c"
    }
    ```

    Names, addresses, dates, identifiers and free text all go through `string`. It is stored under the canonical type name `text`, which `TokenGet` reports back and which you may also send instead.

    There is no `tokenbase`: a string has no format to preserve, so you get a UUID and nothing else. Store the `tokenuuid` in place of the name and detokenize it with `TokenGet`.
  </Tab>

  <Tab title="A credit card">
    ```bash theme={null}
    curl -X POST https://your-databunker/v2/TokenCreate \
      -H "X-Bunker-Token: YOUR_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "tokentype": "creditcard",
        "record": "4532015112830366",
        "unique": true
      }'
    ```

    ```json theme={null}
    {
      "status": "ok",
      "tokenuuid": "106ad147-5559-fdea-a2af-1a1075f0d0f4",
      "tokenbase": "3371363676177808"
    }
    ```

    Here you get both forms. `tokenuuid` addresses the token; `tokenbase` is the format-preserving one — same length, still Luhn-valid, so it drops into a column that already validates card numbers. Either value detokenizes through `TokenGet`.
  </Tab>
</Tabs>

`unique: true` deduplicates: the same input returns the same token instead of minting a new one. Use `TokenCreateBulk` for batches, and `BulkListUnlock` plus `BulkListTokens` to detokenize many at once.

<Note>
  Use `creditcard` and `string` — between them they cover every field. Only `creditcard` returns a format-preserving `tokenbase`; `string` returns a UUID, so do not expect a look-alike value for a name or an address.

  The other types (`unixtimestamp`, `uint32`, `uint64`, `email`, `ssn`, `uuid`) are **not recommended** — they do not scale. See [format-preserving tokenization](/pro/concepts/tokenization#supported-data-types).
</Note>

### What you get

* **Value-level granularity.** A card number can live in a PCI-scoped system while the rest of the profile does not.
* **Deduplication** across the whole vault with `unique`.
* **Format preservation** for `creditcard`, so legacy schemas and card validators keep working.

### What it costs you

* **Each token is a licensed record.** Ten fields tokenized individually is ten records, where the same ten fields inside a profile is one. See [what counts as a record](/pro/get-started/licensing#what-counts-as-a-record).
* **Erasure is yours to manage.** The `tokens` table is not keyed by user token, so `UserDelete` does not touch these tokens and a data-subject erasure request will not reach them. Nothing links a token back to the user's profile, and there is no index to search, so you must record the mapping yourself and delete with `TokenDelete` or `BulkDeleteTokens` — or give each token an expiry, as below. Lose the mapping and only an expiry will ever remove the value.
* **`unique` makes equal values linkable.** The same input always yields the same token, so anyone holding two records can tell they carry the same value without detokenizing either. Leave `unique` off where that inference matters — see [why shared records, not long-lived tokens](/pro/concepts/shared-records).
* **Groups do not apply.** Every group endpoint identifies its member by `mode` and `identity` — a *user*. A standalone token cannot be put in a group, so you cannot organise or query tokenized values that way.
* No indexed lookup, no app data, no versioning, no agreements, no per-user audit trail — those all hang off a user record.

## Share a single field, without a credential

If the goal is to let an outside party read **one field** of a profile, do not tokenize that field separately — create a [shared record](/pro/concepts/shared-records) scoped to it.

```bash theme={null}
curl -X POST https://your-databunker/v2/SharedRecordCreate \
  -H "X-Bunker-Token: YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "token",
    "identity": "8ba70d19-a8b1-0b82-f0b9-d5952221be67",
    "fields": "email",
    "partner": "partner-acme-billing",
    "finaltime": "7d"
  }'
```

```json theme={null}
{ "status": "ok", "recorduuid": "a9ad8567-d6ce-9363-88f3-1f3161515c50" }
```

Hand the `recorduuid` to the consumer. Retrieving it needs **no `X-Bunker-Token`** — that is the point, the partner never receives a vault credential:

```bash theme={null}
curl -X POST https://your-databunker/v2/SharedRecordGet \
  -H "Content-Type: application/json" \
  -d '{"recorduuid":"a9ad8567-d6ce-9363-88f3-1f3161515c50"}'
```

```json theme={null}
{ "status": "ok", "data": { "email": "jane@example.com" } }
```

Only the field named in `fields` comes back. After `finaltime` the UUID stops resolving on its own, with no cleanup call. `partner` tags every retrieval in the audit trail. Repeat the call once per field to hand different fields to different consumers, each with its own expiry.

Add `appname` to share fields from one of the user's app data records instead of the profile.

<Warning>
  Shared records work on **user records only**. There is no equivalent for a standalone `TokenCreate` token, so a field you move out of the profile into its own token can no longer be shared this way. Decide this before you tokenize.
</Warning>

## Expire data automatically

Neither option obliges you to delete anything by hand. Both `UserCreate` and
`TokenCreate` — and their bulk forms — accept an expiry at creation, after which the
record is gone without a call from you.

Two parameters, usable together:

| Parameter     | Meaning                                                                                        |
| ------------- | ---------------------------------------------------------------------------------------------- |
| `finaltime`   | An absolute lifetime. The record is removed once it elapses, however often it was used.        |
| `slidingtime` | A window that restarts on every access. Records that stop being used expire; active ones stay. |

```bash theme={null}
# a profile kept for a year, or 90 days after it was last touched
curl -X POST https://your-databunker/v2/UserCreate \
  -H "X-Bunker-Token: YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "profile": { "email": "jane@example.com", "first": "Jane" },
    "finaltime": "1y",
    "slidingtime": "90d"
  }'
```

```bash theme={null}
# a card token that disappears after 30 days
curl -X POST https://your-databunker/v2/TokenCreate \
  -H "X-Bunker-Token: YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tokentype": "creditcard",
    "record": "4532015112830366",
    "unique": true,
    "finaltime": "30d"
  }'
```

Afterwards an expired profile reports `The user record has expired` and an expired
token reports `Token not found`. Indexed lookups stop resolving as well.

The same two parameters apply to `FileCreate`, `SessionUpsert` and the `XTokenCreate`
calls; `SharedRecordCreate` and `AgreementAccept` take `finaltime` only.

<Note>
  Expiry is the practical answer to the tracking problem in option 2. A standalone token is invisible to `UserDelete`, but a token created with `finaltime` removes itself, so a retention limit does not depend on your bookkeeping being right. Give every per-field token an expiry unless you have a reason not to.
</Note>

## Choosing

|                                                                 | Whole profile                                                  | Per-field tokens                                                                                 |
| --------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| What your application stores                                    | one UUID for the whole profile                                 | one token per value — `creditcard` also returns a Luhn-valid look-alike                          |
| Licensed records for 10 fields                                  | 1                                                              | 10                                                                                               |
| Indexed lookup                                                  | `email`, `phone`, `login`, `custom`                            | none                                                                                             |
| Erasure                                                         | `UserDelete` / `UserDeleteBulk`, by token or any indexed field | `TokenDelete` / `BulkDeleteTokens`, by token only — nothing links one back to the user's profile |
| Automatic erasure                                               | `finaltime`, `slidingtime`                                     | `finaltime`, `slidingtime`                                                                       |
| Groups, app data, shared records, versioning, agreements, audit | yes                                                            | no                                                                                               |
| A non-indexed value two users share                             | stored on each profile                                         | one token for both, with `unique`                                                                |

**Store the whole profile** for anything that is a user — it is the cheaper, more capable default, and it is what the [PII vault](/pro/get-started/pii-vault) is for.

**Tokenize a field on its own** when the value has to leave the user record: a card number crossing into a PCI-scoped system, or a value that must keep its original format for a legacy schema.

**Mix them** for the common case. Keep the profile in the vault, and tokenize only the one high-risk field separately, storing its `tokenuuid` inside the profile:

```json theme={null}
{
  "profile": {
    "email": "jane@example.com",
    "first": "Jane",
    "creditcard_token": "106ad147-5559-fdea-a2af-1a1075f0d0f4"
  }
}
```

Remember that the separately tokenized value is now outside the reach of `UserDelete` — delete it explicitly when erasing the user.

## Related

* [PII vault](/pro/get-started/pii-vault) — how whole-profile tokenization works
* [Format-preserving tokenization](/pro/concepts/tokenization) — token types, expiration, bulk operations
* [Shared records](/pro/concepts/shared-records) — sharing a scoped, expiring view of a record
* [Licensing and limits](/pro/get-started/licensing#what-counts-as-a-record) — what counts as a record
