NAV
Reactor Proxy

Overview

Reference

More

Introduction

Expressions provide a flexible templating language that can be used to apply custom transformations to token data.

Expressions can be used within many API endpoints, including:

Language

The expression language used within the Basis Theory API is based on the Liquid template language. While all valid Liquid syntax is supported, Basis Theory expressions are typically formed from a combination of objects and filters.

Objects

Objects contain content that will be rendered when the expression is evaluated, and they are formed by wrapping a statement within double curly braces. For example, {{ data.card_number }} would be evaluated by rendering the card_number property within the data object.

The variables that are available for reference within an object expression depends on the context in which the expression is used. When including expressions during token creation (e.g. within search_indexes, fingerprint_expression or mask), the data variable is automatically bound to the token's data value. metadata is also available for expressions within fingerprint_expression.

When including expressions within Reactor or Proxy requests, token ids may be specified within object expressions, and each token id will be bound to the corresponding token's data value. See detokenization for further details about this process.

Filters

Oftentimes data may be tokenized in one format, but you wish to use this data in a different format within a request. Several pieces of data may be stored together in a single token (e.g. a user record containing first and last names and contact information), but you wish to only use a single piece of that data within a Proxy or Reactor request, or you may wish to reformat the data before indexing it for search (e.g. use only the last name, but normalize by uppercasing it).

To allow you complete flexibility, transformation functions called filters can be applied within any expression. Generally, a variable's value can be transformed by specifying a filter after the | symbol:
{{ <variable> | <filter> }}

You can effectively think of this as "piping" a variable into the filter in the same way you may be familiar with the "pipe" operator from Unix-like systems. Multiple filters can be chained together by "piping" the result of each filter into the next, applying in order from left to right:
{{ <variable> | <filter1> | <filter2> | ... }}

All standard Liquid filters are supported within expressions. For example, given a token containing a name object containing both first and last name properties:

{
  "data": {
    "name": "John Doe"
  },
  ...
}

We can create an expression to return the upper-cased last name by splitting on the space character, grabbing the last element, and upper-casing:

{{ data.name | split: ' ' | last | upcase }}

This expression would evaluate to the value "DOE".

In addition to the standard Liquid filters, several custom filters are also available:

alias_preserve_format

Randomly generates a unique alias that preserves the format of the input string, optionally revealing a specified number of characters from the beginning and end of the value.

Alpha characters are replaced with randomized alpha characters, numeric characters are replaced with randomized numeric characters, and special characters and whitespace are preserved.

Parameters

Position Name Type Required Default Value Description
0 reveal_first_length int false 0 The number of characters to reveal from the beginning of the value
1 reveal_last_length int false 0 The number of characters to reveal from the end of the value

Examples

Given a token with the data:

{
  "id": "<expression>",
  "type": "token",
  "data": "ABC12345DEF67890"
}
Expression Example Result
{{ data | alias_preserve_format }} "xir83203hqn73599"
{{ data | alias_preserve_format: 3 }} "ABC83203hqn73599"
{{ data | alias_preserve_format: 0, 5 }} "xir83203hqn67890"
{{ data | alias_preserve_format: 3, 5 }} "ABC83203hqn67890"

alias_preserve_length

Randomly generates a unique alias that preserves the length of the input string, optionally revealing a specified number of characters from the beginning and end of the value.

All characters are replaced with randomized alphanumeric characters. The type of the character in each position is not preserved, e.g. alpha characters may be replaced with numeric characters and vice versa. Special characters and whitespace are not preserved and will be replaced with alphanumeric characters.

Parameters

Position Name Type Required Default Value Description
0 reveal_first_length int false 0 The number of characters to reveal from the beginning of the value
1 reveal_last_length int false 0 The number of characters to reveal from the end of the value

Examples

Given a token with the data:

{
  "id": "<expression>",
  "type": "token",
  "data": "ABC12345DEF67890"
}
Expression Example Result
{{ data | alias_preserve_length }} "v38anr9m2cx0giw7"
{{ data | alias_preserve_length: 3 }} "ABCanr9m2cx0giw7"
{{ data | alias_preserve_length: 0, 5 }} "v38anr9m2cx67890"
{{ data | alias_preserve_length: 3, 5 }} "ABCanr9m2cx67890"

json

Evaluates a JSON Path expression (proposed spec) on the input object.

All standard JSON Path syntax is supported, provided that the expression resolves to a single value. If the expression resolves to multiple values, the request will result in a 400 error.

While Liquid supports a very similar syntax to JSON path when selecting properties within a JSON object (e.g. {{ data.bicycle.color }}), it does not support more complex JSON Path expressions (e.g. array filter expressions like $.books[?(@.price < 10)].title). The json filter provides further flexibility for evaluating complex JSON Path expressions.

Parameters

Position Name Type Required Description
0 json_path_expression string true A JSON Path expression

Examples

Given a token with the data:

{
  "id": "d35412f4-9d3b-45d8-b051-fe4b7d4e14c5",
  "type": "token",
  "data": { 
    "books": [
      { 
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { 
        "category": "fantasy",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}
Expression Result
{{ data.bicycle.color }} "red"
{{ data | json: '$.bicycle.color' }} "red"
{{ data.bicycle }} { "color": "red", "price": 19.95 }
{{ data | json: '$.books[0].author' }} "Herman Melville"
{{ data | json: '$.books[?(@.price < 10)].title' }} "Moby Dick"
{{ data.nonexistent }} null
{{ data | json: '$.book..author' }} <400 Error>

last4

Returns the last 4 characters of a string. If the string's length is less than 4, the whole value is returned.

Parameters

None

Examples

Given a token with the data:

{
  "id": "d35412f4-9d3b-45d8-b051-fe4b7d4e14c5",
  "type": "token",
  "data": "36227206271667"
}
Expression Result
{{ data | last4 }} "1667"
{{ data | slice: 12, 2 | last4 }} "67"

reveal

Returns a masked version of the string revealing characters at the start and end whilst preserving others. If the string's length is less than or equal to reveal_first + reveal_last, or the resulting masked string equals the original unmasked value, the whole value is masked.

Parameters

Position Name Type Required Default Value Description
0 reveal_first int false 0 The number of characters to reveal at the start
1 reveal_last int false 0 The number of characters to reveal at the end
2 mask_char char false X A masking character
3 preserve_chars string false `` The characters to preserve

Examples

Given a token with the data:

{
  "id": "d35412f4-9d3b-45d8-b051-fe4b7d4e14c5",
  "type": "token",
  "data": "3622-7206-2716-5567"
}
Expression Result
{{ data | reveal: 6 }} "3622-7XXXXXXXXXXXXX"
{{ data | reveal: 7, 5 }} "3622-72XXXXXXX-5567"
{{ data | reveal: 7, 5, '#' }} "3622-72#######-5567"
{{ data | reveal: 7, 4, '#', "-" }} "3622-72##-####-5567"
{{ data | reveal: 10, 9 }} "XXXXXXXXXXXXXXXXXXX"

pad_left

Returns a new string of the desired length by padding the input string on the left with the specified padChar.

Returns null when provided a null input value.

Parameters

Position Name Type Required Description
0 length int true The number of characters in the resulting string
1 pad_char char true A padding character

Examples

Given a token with the data:

{
  "id": "d35412f4-9d3b-45d8-b051-fe4b7d4e14c5",
  "type": "token",
  "data": "1234"
}
Expression Result
{{ data | pad_left: 6, '0' }} "001234"
{{ data | pad_left: 6, 'X' }} "XX1234"
{{ data | pad_left: 4, '0' }} "1234"
{{ data | pad_left: 2, '0' }} "1234"

pad_right

Returns a new string of the desired length by padding the input string on the right with the specified padChar.

Returns null when provided a null input value.

Parameters

Position Name Type Required Description
0 length int true The number of characters in the resulting string
1 pad_char char true A padding character

Examples

Given a token with the data:

{
  "id": "d35412f4-9d3b-45d8-b051-fe4b7d4e14c5",
  "type": "token",
  "data": "1234"
}
Expression Result
{{ data | pad_right: 6, '0' }} "123400"
{{ data | pad_right: 6, 'X' }} "1234XX"
{{ data | pad_right: 4, '0' }} "1234"
{{ data | pad_right: 2, '0' }} "1234"

reveal_last

Returns the last length characters of a string. If the string's length is less than or equal to length, the whole value is returned.

Parameters

Position Name Type Required Default Value Description
0 length int true null The number of characters to reveal
1 mask_char char false X A masking character

Examples

Given a token with the data:

{
  "id": "d35412f4-9d3b-45d8-b051-fe4b7d4e14c5",
  "type": "token",
  "data": "36227206271667"
}
Expression Result
{{ data | reveal_last: 6 }} "XXXXXXXX271667"
{{ data | reveal_last: 3, '#' }} "###########667"

stringify

Returns a JSON serialized string of the input object.

Returns null when provided a null input value.

Parameters

None

Examples

Given a token with the data:

{
  "id": "d35412f4-9d3b-45d8-b051-fe4b7d4e14c5",
  "type": "token",
  "data": { 
    "books": [
      { 
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { 
        "category": "fantasy",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycles": [{
      "color": "red",
      "price": 19.95
    }, {
      "color": "blue",
      "price": 24.95
    }]
  }
}
Expression Result
{{ data.books[0].price | stringify }} "8.99"
{{ data.books[1].title | stringify }} "The Lord of the Rings"
{{ data.bicycles[1] | stringify }} "{\"color\":\"red\",\"price\":19.95}"
{{ data.bicycles | stringify }} "[{\"color\":\"red\",\"price\":19.95},{\"color\":\"blue\",\"price\":24.95}]"

Detokenization

Detokenization refers to the process by which non-sensitive token identifiers are replaced with the original token data represented by those tokens. Basis Theory supports detokenization through the use of expressions within our serverless Reactor platform and the Proxy.

Detokenization is performed whenever a detokenization expression is identified within a request. In their simplest form, these are Liquid objects of the form {{<tokenId>}}. This expression will be replaced with the token data contained within the token with ID <tokenId>.

Any string value contained in a detokenization expression is expected to represent the id of a token within your tenant. If no such token is found with that identifier, the request will be rejected with a 400 error.

If a token is found with the given identifier, but the calling application is missing permission to use that token, then the request will be rejected with a 403 error. See the following links for more information about the permissions required to use tokens in a Reactor or Proxy.

Token data can also be transformed before including it in a request by applying filters within an expression. In general, filters are applied during detokenization using the syntax:
{{ <tokenId> | <filter1> | <filter2> | ... }}

Check out the detokenization examples below to see some examples of filters in action.

Detokenizing Primitive Data

Tokens containing primitive data values, such as a single string or numeric value, can be easily detokenized within a request just by substituting the token data in place of the detokenization expression. For example, say you have the token:

{
  "id": "1d08babf-456a-4bef-993d-aece3c1a2f66",
  "type": "social_security_number",
  "data": "111-22-3333"
}

Then a request containing the detokenization expression:

{
  "ssn": "{{1d08babf-456a-4bef-993d-aece3c1a2f66}}"
}

will be detokenized into the request:

{
  "ssn": "111-22-3333"
}

Detokenizing Complex Data

Card, Bank, or generic tokens containing complex JSON objects can all be detokenized as well. Detokenization of complex token data is performed by embedding the token's JSON data within the request. For example, say you have the card token:

{
  "id": "5c20545b-52dc-4a60-b9b5-5b7c84f22369",
  "type": "card",
  "data": {
    "number": "4242424242424242",
    "expiration_month": 12,
    "expiration_year": 2025,
    "cvc": "123"
  }
}

Then you can embed the entire Card object within a request with the detokenization expression:

{
  "card": "{{5c20545b-52dc-4a60-b9b5-5b7c84f22369}}"
}

which will be detokenized into the request:

{
  "card": {
    "number": "4242424242424242",
    "expiration_month": 12,
    "expiration_year": 2025,
    "cvc": "123"
  }
}

Examples

Each of the use cases shown below include an example for both Reactors and the Proxy - select the appropriate tab at the top of the right-hand column to choose which examples to show.

Reactor Prerequisites

For the examples below, we will be using a Reactor created using the Spreedly - Card Reactor Formula. We will need the id of this Reactor in the examples below: d08bc998-9301-495c-a2e5-04f8dc0916b4.

This Reactor Formula accepts the following request parameters:

name type optional
card.number string false
card.expiration_month number false
card.expiration_year number false
card.cvc string true
card_owner_full_name string false

Proxy Prerequisites

Also in the examples below, we will proxy requests to Spreedly using their Payment Methods API. This is the same request that is being made from within the Spreedly - Card Reactor Formula.

This API endpoint accepts POST requests with a body of the form:

{
  "payment_method": {
    "credit_card": {
      "number": "4242424242424242",
      "month": "12",
      "year": "2025",
      "verification_value": "123",
      "full_name": "Card Owner"
    },
    "retained": true
  }
}

and it authenticates using basic auth - we will be passing a simulated value in the Authorization header on the example proxy requests - replace this with your own authentication credentials if you want to follow along.

Use Complex Tokens

Original Request

curl "https://api.basistheory.com/reactors/d08bc998-9301-495c-a2e5-04f8dc0916b4/react" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "args": {
              "card": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c}}",
              "card_owner_full_name": "John Doe"
            }
        }'
curl "https://api.basistheory.com/proxy" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "BT-PROXY-URL: https://core.spreedly.com/v1/payment_methods.json" \
      -H "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "payment_method": {
              "credit_card": {
                "number": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.number'}}",
                "month": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.expiration_month'}}",
                "year": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.expiration_year'}}",
                "verification_value": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.cvc'}}",
                "full_name": "John Doe"
              },
              "retained": true
            }
          }'

Detokenized Request

{
  "args": {
    "card": {
      "number": "4242424242424242",
      "expiration_month": 11,
      "expiration_year": 2025,
      "cvc": "123"
    },
    "card_owner_full_name": "John Doe"
  }, 
  "configuration": {...}
}
{
  "payment_method": {
    "credit_card": {
      "number": "4242424242424242",
      "month": "11",
      "year": "2025",
      "verification_value": "123",
      "full_name": "John Doe"
    },
    "retained": true
  }
}

In this example, we show how you can use a Card token to create a Spreedly payment method.

Say you have created the card token:

{
  "id": "815029c2-29ec-4fc2-8cd4-99feb3ee582c",
  "type": "card",
  "data": {
    "number": "4242424242424242", 
    "expiration_month": 11,
    "expiration_year": 2025,
    "cvc": "123"
  }
}

We have not tokenized the card owner's name, John Doe. Assume that we have this plaintext value directly available to our application to pass into the Reactor or Proxy request.

Override a Field on a Token

Original Request

curl "https://api.basistheory.com/reactors/d08bc998-9301-495c-a2e5-04f8dc0916b4/react" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "args": {
              "card": {
                "number": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.number'}}",
                "expiration_month": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.expiration_month'}}",
                "expiration_year": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.expiration_year'}}",
                "cvc": "987"
              },
              "card_owner_full_name": "John Doe"
            }
        }'
curl "https://api.basistheory.com/proxy" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "BT-PROXY-URL: https://core.spreedly.com/v1/payment_methods.json" \
      -H "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "payment_method": {
              "credit_card": {
                "number": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.number'}}"
                "month": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.expiration_month'}}",
                "year": "{{815029c2-29ec-4fc2-8cd4-99feb3ee582c | json: '$.expiration_year'}}",
                "verification_value": "987",
                "full_name": "John Doe"
              },
              "retained": true
            }
          }'

Detokenized Request

{
  "args": {
    "card": {
      "number": "4242424242424242",
      "expiration_month": 11,
      "expiration_year": 2025,
      "cvc": "987"
    },
    "card_owner_full_name": "John Doe"
  }, 
  "configuration": {...}
}
{
  "payment_method": {
    "credit_card": {
      "number": "4242424242424242",
      "month": "11",
      "year": "2025",
      "verification_value": "987",
      "full_name": "John Doe"
    },
    "retained": true
  }
}

In this example, we show how you can use a Card token to create a Spreedly payment method, but provide an updated CVC (987) that is different from the cvc value stored within the token. This could be desired if the updated CVC was collected directly from a user interface, possibly as a challenge to the user to prove they own the card.

Here, we will be using the same card token and card owner name from the previous Use Complex Tokens example.

Use Multiple Tokens

Original Request

curl "https://api.basistheory.com/reactors/d08bc998-9301-495c-a2e5-04f8dc0916b4/react" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "args": {
              "card": {
                "number": "{{d9939ddc-d7be-423b-a0f5-69f65fec57df}}"
                "expiration_month": 10,
                "expiration_year": 2024,
                "cvc": "789"
              },
              "card_owner_full_name": "{{f4d86311-1254-4155-b532-b651279a8cc0}}"
            }
        }'
curl "https://api.basistheory.com/proxy" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "BT-PROXY-URL: https://core.spreedly.com/v1/payment_methods.json" \
      -H "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "payment_method": {
              "credit_card": {
                "number": "{{d9939ddc-d7be-423b-a0f5-69f65fec57df}}",
                "month": "10",
                "year": "2024",
                "verification_value": "789",
                "full_name": "{{f4d86311-1254-4155-b532-b651279a8cc0}}"
              },
              "retained": true
            }
          }'

Detokenized Request

{
  "args": {
    "card": {
      "number": "5555555555554444",
      "expiration_month": 10,
      "expiration_year": 2024,
      "cvc": "789"
    },
    "card_owner_full_name": "Jane Doe"
  }, 
  "configuration": {...}
}
{
  "payment_method": {
    "credit_card": {
      "number": "5555555555554444",
      "month": "10",
      "year": "2024",
      "verification_value": "789",
      "full_name": "Jane Doe"
    },
    "retained": true
  }
}

In this example, we will show how you can use multiples tokens - a Card Number token and a generic PII token to hold the card owner's name:

{
  "id": "d9939ddc-d7be-423b-a0f5-69f65fec57df",
  "type": "card_number",
  "data": "5555555555554444",
  "privacy": {
    "classification": "pci",
    "impact_level": "high"
  }
}
{
  "id": "f4d86311-1254-4155-b532-b651279a8cc0",
  "type": "token",
  "data": "Jane Doe",
  "privacy": {
    "classification": "pii",
    "impact_level": "moderate"
  }
}

In this example, we have not tokenized the card's expiration date or CVC - say our application accepts these values in plaintext and forwards them directly into the request.

Combine Multiple Tokens within a Single Argument

Original Request

curl "https://api.basistheory.com/reactors/d08bc998-9301-495c-a2e5-04f8dc0916b4/react" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "args": {
              "card": "{{b78b4bee-5499-42dd-8671-f1d23d32355b}}",
              "card_owner_full_name": "{{523949a9-e32f-4b5b-a0ad-7a435c79deb4}} {{42af9170-e6ca-4ea7-a43b-730a0b47b6d0}}"
            }
        }'
curl "https://api.basistheory.com/proxy" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "BT-PROXY-URL: https://core.spreedly.com/v1/payment_methods.json" \
      -H "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "payment_method": {
              "credit_card": {
                "number": "{{b78b4bee-5499-42dd-8671-f1d23d32355b | json: '$.number'}}",
                "month": "{{b78b4bee-5499-42dd-8671-f1d23d32355b | json: '$.expiration_month'}}",
                "year": "{{b78b4bee-5499-42dd-8671-f1d23d32355b | json: '$.expiration_year'}}",
                "verification_value": "{{b78b4bee-5499-42dd-8671-f1d23d32355b | json: '$.cvc'}}",
                "full_name": "{{523949a9-e32f-4b5b-a0ad-7a435c79deb4}} {{42af9170-e6ca-4ea7-a43b-730a0b47b6d0}}"
              },
              "retained": true
            }
          }'

Detokenized Request

{
  "args": {
    "card": {
      "number": "5105105105105100",
      "expiration_month": 5,
      "expiration_year": 2025,
      "cvc": "123"
    },
    "card_owner_full_name": "John Brown"
  }, 
  "configuration": {...}
}
{
  "payment_method": {
    "credit_card": {
      "number": "5105105105105100",
      "month": "5",
      "year": "2025",
      "verification_value": "123",
      "full_name": "John Brown"
    },
    "retained": true
  }
}

In this example, we will show how you can combine the data from multiple tokens within a single field on a request. Say we have chosen to store the card holder's first and last names as separate tokens:

{
  "id": "523949a9-e32f-4b5b-a0ad-7a435c79deb4",
  "type": "token",
  "data": "John"
}
{
  "id": "42af9170-e6ca-4ea7-a43b-730a0b47b6d0",
  "type": "token",
  "data": "Brown"
}

Also, we have the card token:

{
  "id": "b78b4bee-5499-42dd-8671-f1d23d32355b",
  "type": "card",
  "data": {
    "number": "5105105105105100", 
    "expiration_month": 5,
    "expiration_year": 2025,
    "cvc": "123"
  }
}

Then we can concatenate the first and last name tokens within the request as shown in the sample requests.

Using Custom Token Schemas

Original Request

curl "https://api.basistheory.com/reactors/d08bc998-9301-495c-a2e5-04f8dc0916b4/react" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "args": {
              "card": {
                "number": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.card_number'}}",
                "expiration_month": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.exp_month'}}",
                "expiration_year": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.exp_year'}}",
              },
              "card_owner_full_name": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.owner.first_name'}} {{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.owner.last_name'}}"
            }
        }'
curl "https://api.basistheory.com/proxy" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "BT-PROXY-URL: https://core.spreedly.com/v1/payment_methods.json" \
      -H "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "payment_method": {
              "credit_card": {
                "number": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.card_number'}}",
                "month": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.exp_month'}}",
                "year": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.exp_year'}}",
                "full_name": "{{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.owner.first_name'}} {{9a48a051-972b-4569-8fd5-cbe17a604f96 | json: '$.card.owner.last_name'}}"
              },
              "retained": true
            }
          }'

Detokenized Request

{
  "args": {
    "card": {
      "number": "4000056655665556",
      "expiration_month": 4,
      "expiration_year": 2026,
    },
    "card_owner_full_name": "John Smith"
  }, 
  "configuration": {...}
}
{
  "payment_method": {
    "credit_card": {
      "number": "4000056655665556",
      "month": "4",
      "year": "2026",
      "full_name": "John Smith"
    },
    "retained": true
  }
}

In this example, we will store our card data within a custom generic token that contains additional fields relevant to our application:

{
  "id": "9a48a051-972b-4569-8fd5-cbe17a604f96",
  "type": "token",
  "data": {
    "card": {
      "card_number": "4000056655665556",
      "exp_month": 4,
      "exp_year": 2026,
      "owner": {
        "first_name": "John",
        "middle_name": "Andrew",
        "last_name": "Smith"
      }
    },
    "billing_address": {
      "street_address": "175 5th Ave",
      "city": "New York",
      "state": "NY",
      "zip": "10010"
    }
  },
  "privacy": {
    "classification": "pci",
    "impact_level": "high"
  }
}

Notice that the card owner's full name is constructed by concatenating the card.owner.first_name and card.owner.last_name properties into a single string value.

Transforming Token Data

Original Request

curl "https://api.basistheory.com/reactors/d08bc998-9301-495c-a2e5-04f8dc0916b4/react" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "args": {
              "card": {
                "number": "{{67d76cd0-bc2a-4a73-be8c-6539c81db465 | json: '$.card_number'}}",
                "expiration_month": "{{67d76cd0-bc2a-4a73-be8c-6539c81db465 | json: '$.expiration_month' | pad_left: 2, '0'}}",
                "expiration_year": "{{67d76cd0-bc2a-4a73-be8c-6539c81db465 | json: '$.expiration_year' | slice: -2, 2}}",
              },
              "card_owner_full_name": "John Doe"
            }
        }'
curl "https://api.basistheory.com/proxy" \
      -H "BT-API-KEY: key_NS21v84n7epsSc5WzoFjM6" \
      -H "BT-PROXY-URL: https://core.spreedly.com/v1/payment_methods.json" \
      -H "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" \
      -H "Content-Type: application/json" \
      -X "POST" \
      -d '{
            "payment_method": {
              "credit_card": {
                "number": "{{67d76cd0-bc2a-4a73-be8c-6539c81db465 | json: '$.card_number'}}",
                "month": "{{67d76cd0-bc2a-4a73-be8c-6539c81db465 | json: '$.expiration_month' | pad_left: 2, '0'}}",
                "year": "{{67d76cd0-bc2a-4a73-be8c-6539c81db465 | json: '$.expiration_year' | slice: -2, 2}}",
                "full_name": "John Doe"
              },
              "retained": true
            }
          }'

Detokenized Request

{
  "args": {
    "card": {
      "number": "4000056655665556",
      "expiration_month": "03",
      "expiration_year": "25"
    },
    "card_owner_full_name": "John Smith"
  }, 
  "configuration": {...}
}
{
  "payment_method": {
    "credit_card": {
      "number": "4000056655665556",
      "month": "03",
      "year": "25",
      "full_name": "John Doe"
    },
    "retained": true
  }
}

In this example, we will be using the card token:

{
  "id": "67d76cd0-bc2a-4a73-be8c-6539c81db465",
  "type": "card",
  "data": {
    "number": "4242424242424242", 
    "expiration_month": 3,
    "expiration_year": 2025,
    "cvc": "123"
  }
}

We will transform the expiration_month property within this token to be padded on the left with a 0 to two characters, and to only provide the last two digits of the expiration year.

Aliasing

By default, when creating a token, a random ID (v4 UUID) will be generated to represent the token and its secure payload. Token IDs can be safely stored within your system or transmitted within API requests without the risk of revealing sensitive information.

While randomly generated IDs may be sufficient for many use cases, there are several scenarios in which it can be valuable for a generated token ID to satisfy a specific format, or to allow a custom token ID to be specified within the request:

  • You would like the ID to resemble the format of the underlying token data (e.g. to satisfy validation constraints)
  • Due to technical constraints, your system requires the token ID to have a prescribed size or format (e.g. to store the ID within a legacy database column having a certain data type)
  • You wish to pre-generate token IDs within your system
  • You wish to define your own custom algorithm for generating token IDs

Basis Theory supports these use cases in two ways:

  1. Accepting a predefined token id when creating a token
  2. Specifying how an ID should be generated through the use of Alias Expressions

Predefined Token IDs

A token ID can be pre-generated by your system and provided when creating a token within the id property. Any non-empty string value between 3 and 400 characters can be specified as an ID, as long as there is not another token within your tenant with the same ID. If a duplicate ID is found, a 409 CONFLICT response will be returned.

Alias Expressions

Alias expressions allow you to leverage the full expression language to specify a token's ID via a dynamically evaluated expression, and can be provided within the id property of the create token or tokenize requests.

You are able to reference the data property within an object expression - data will be bound to the provided token data.

Typically, alias expressions are used in conjunction with one of the alias_* filters (e.g. alias_preserve_format or alias_preserve_length). These filters randomly generate a secure pseudonym based on the token's data. For example, if a token contains the credit card number 4242-4242-4242-4242, the alias expression {{ data | alias_preserve_format: 0, 4 }} could be used to reveal the last 4 digits in the ID while replacing all the other non-revealed digits with random digits, resulting in an ID of the form 9326-7128-4203-4242.

Best Practices

While you have the ability to set any value as a token's ID, you should never reveal sensitive information in IDs. The id property will not be encrypted and will be revealed in plaintext within API requests and responses, and this value will likely be referenced within your systems.

Only reveal characters at the beginning or end of your token data if absolutely necessary, and if so, reveal the minimum number of characters possible. Every revealed character makes it easier for a malicious actor to try to uncover the underlying token's data. For example, given a 16 digit credit card number, revealing the first 6 and last 4 characters in a format preserving alias only leaves 6 remaining digits (10^6 = 1 million possible combinations), which can be reduced further knowing that the card number must have a valid Luhn checksum. Instead, consider revealing only the first 6 or last 4 digits, but not both.

Limitations

Token IDs may not contain any of the following special characters: #, /, \, +, ?, [, ], |, &, =, %, <, >, {, }, ^

Including any restricted special characters in an ID will result in a 400 BAD REQUEST response.

Since token IDs are included within the URL route on some operations, some allowable special characters and whitespace characters will need to be URL encoded within requests where the ID is provided within the API route. The latest official Basis Theory SDKs automatically URL encode token IDs when they are included in the URL.

Examples

SSN with Last 4 Digits Preserved

Request:

{
  "id": "{{ data | alias_preserve_format: 0, 4 }}",
  "type": "token",
  "data": "111-22-3333"
}

Example ID Value: 830-46-3333

Card Number with Last 4 Digits Preserved

Request:

{
  "id": "{{ data.number | alias_preserve_format: 0, 4 }}",
  "type": "card",
  "data": {
    "number": "4242424242424242",
    "expiration_month": 3,
    "expiration_year": 2025,
    "cvc": "456"
  }
}

Example ID Value: 9273610263384242

Email with Domain Preserved

This example assumes you do not know the length of the email domain, and want to preserve arbitrary domains.

Request:

{
  "id": "{{ data.email | split: '@' | first | alias_preserve_length }}@{{ data.email | split: '@' | last }}",
  "type": "token",
  "data": {
    "first_name": "John",
    "last_name": "Doe",
    "email": "[email protected]"
  }
}

Example ID Value: [email protected]

If you know the length of the domain ahead of time, you can simplify this expression by revealing the number of characters in the domain, e.g. {{ data.email | alias_preserve_length: 0, 10 }}.

Fingerprints

A fingerprint can be generated at the time a token is created, which can be used to uniquely identify the contents of a token for its type. You will get different fingerprints for the same content across different token types, but the same fingerprints for the same content across the same token types. Fingerprints are cryptographically secure and cannot be reversed to recover the original token's data, so they are safe to store in your application and used to compare tokens without retrieving plaintext token data (e.g. for token de-duplication).

When creating a token, fingerprint expression can be specified within the request. You are able to reference the data and metadata variable within an object expression - data and metadata will be bound to the provided token data and metadata, respectively.

Examples

Fingerprinting Primitive Tokens

Request:

{
  "type": "token",
  "data": "111-22-3333",
  "fingerprint_expression": "{{ data }}"
}

Fingerprinted Value: "111-22-3333"

Fingerprinting a Property of a Complex Token

Request:

{
  "type": "token",
  "data": {
    "bank": {
      "routing_number": "021000021",
      "account_number": "1234567890",
      "account_owner": {
        "first_name": "John",
        "middle_name": "Andrew",
        "last_name": "Smith"
      }
    }
  },
  "fingerprint_expression": "{{ data.account_number }}"
}

Fingerprinted Value: "1234567890"

Fingerprinting Multiple Properties of a Complex Token

Request:

{
  "type": "token",
  "data": {
    "bank": {
      "routing_number": "021000021",
      "account_number": "1234567890",
      "account_owner": {
        "first_name": "John",
        "middle_name": "Andrew",
        "last_name": "Smith"
      }
    }
  },
  "fingerprint_expression": "{{ data.bank.routing_number }}|{{ data.bank.account_number }}"
}

Fingerprinted Value: "021000021|1234567890"

Transforming Data Before Fingerprinting

Request:

{
  "type": "token",
  "data": {
    "name": {
      "first_name": "John",
      "middle_name": "Andrew",
      "last_name": "Smith"
    }
  },
  "fingerprint_expression": "{{ data.name.first_name | upcase }} {{ data.last_name | upcase }}"
}

Fingerprinted Value: "JOHN SMITH"

Masks

Masks allow you to reformat token data into a non-sensitive representation that can be safely exposed. For example, if the original credit card number was 4242-4242-4242-4242, a common mask pattern is to only show the last 4 digits, such as XXXX-XXXX-XXXX-4242.

When creating a token, the mask can be specified within the request. You are able to reference the data property within an object expression - data will be bound to the provided token data.

When retrieving a token with inadequate read permissions, returned token data is either masked or redacted based on the token's data restriction policy. For tokens with the mask restriction policy, the token's mask will be evaluated and returned within the data property. For tokens with the redact restriction policy, data will not be returned and a mask cannot be defined. The mask property is required for tokens with type token having a data restriction policy of mask. All other token types have a default mask assigned in case a custom one is not provided.

Examples

Masking Primitive Tokens

Request:

{
  "type": "token",
  "data": "4242-4242-4242-4242",
  "mask": "{{ data | reveal_last: 4 }}",
  ...
}

Response:

{
  "type": "token",
  "data": "XXXX-XXXX-XXXX-4242",
  "mask": "{{ data | reveal_last: 4 }}",
  ...
}

Masking Complex Tokens

Request:

{
  "type": "token",
  "data": {
    "name": { 
      "first": "John", 
      "middle": "Wilson", 
      "last": "Doe" 
    },
    "ssn": "123-45-6789",
    "card_number": "4242-4242-4242-4242",
    "cvc": "123"
  },
  "mask": {
    "name": {
      "first": "{{ data.name.first }}",
      "last": "{{ data.name.last | slice: 0, 1 }}."
    },
    "ssn": "{{ data.ssn | reveal_last: 4, '#' }}",
    "card_number": "{{ data.card_number | reveal_last: 4, '#' }}"
  },
  ...
}

Response:

{
  "type": "token",
  "data": {
    "name": { 
      "first": "John",  
      "last": "D." 
    },
    "ssn": "###-##-6789",
    "card_number": "####-####-####-4242"
  },
  "mask": {
    "name": {
      "first": "{{ data.name.first }}",
      "last": "{{ data.name.last | slice: 0, 1 }}."
    },
    "ssn": "{{ data.ssn | reveal_last: 4, '#' }}",
    "card_number": "{{ data.card_number | reveal_last: 4, '#' }}"
  },
  ...
}

Search Indexes

Several token attributes can be searched through the token search API. A token's data can also be searched, provided that the token has been indexed for search at the time of its creation.

When creating a token, an array of search index expressions can be specified within the request. You are able to reference the data variable within object expressions - data will be bound to the provided token data, which may either be a complex or primitive value, based on your usage.

Examples

Indexing Primitive Tokens

Request:

{
  "type": "token",
  "data": "111-22-3333",
  "search_indexes": [
    "{{ data }}",
    "{{ data | last4 }}",
    "{{ data | remove: '-' }}"
  ]
}

Created Indexes:

  • "111-22-3333"
  • "3333"
  • "111223333"

Indexing Properties of a Complex Token

Request:

{
  "type": "token",
  "data": {
    "card": {
      "card_number": "4000056655665556",
      "exp_month": 4,
      "exp_year": 2026,
      "owner": {
        "first_name": "John",
        "middle_name": "Andrew",
        "last_name": "Smith"
      }
    },
    "billing_address": {
      "street_address": "175 5th Ave",
      "city": "New York",
      "state": "NY",
      "zip": "10010"
    }
  },
  "search_indexes": [
    "{{ data.card.card_number }}",
    "{{ data.card.owner.last_name }}",
    "{{ data.billing_address.zip }}"
  ]
}

Created Indexes:

  • "4000056655665556"
  • "Smith"
  • "10010"

Transforming Data Before Indexing

Request:

{
  "type": "token",
  "data": {
    "card": {
      "card_number": "4000056655665556",
      "exp_month": 4,
      "exp_year": 2026,
      "owner": {
        "first_name": "John",
        "middle_name": "Andrew",
        "last_name": "Smith"
      }
    },
    "billing_address": {
      "street_address": "175 5th Ave",
      "city": "New York",
      "state": "NY",
      "zip": "10010"
    }
  },
  "search_indexes": [
    "{{ data.card.card_number | last4 }}",
    "{{ data.card.owner.last_name | upcase }}",
    "{{ data.card.owner.first_name | upcase }} {{ data.card.owner.last_name | upcase }}",
    "{{ data.billing_address.zip | slice: 0, 5 }}"
  ]
}

Created Indexes:

  • "5556"
  • "SMITH"
  • "JOHN SMITH"
  • "10010"