API ReferenceChangelog

ⓘ Reading Time: 20 mins

Transaction rules provide a mechanism for creating card-specific rules that get evaluated at enrichment time against enrichment outputs. For instance, you can set up rules to ensure a card is only used at a specific merchant, or to ensure a card is only used for certain spend categories, or even a combination of the two.

This guide shows you how to create different types of transaction rules. We recommend working along as you go, using your API key for our sandbox environment.

Transaction rules are not enabled by default. To request access, please contact us at [email protected].

For simplicity, the examples in this guide show you how to send requests to Spade directly. However, you may want to build an interface for creating transaction rules into client applications. Because client-side code is public and your API key is secret, you will need to proxy merchant search and transaction rule requests through your backend to Spade's servers. The Merchant Search Guide demonstrates how to proxy requests through your backend to Spade's API.

Request proxying

⚠ NEVER send your API key to the client. This includes any client-side application code bundles (websites, mobile apps, etc...).

About transaction rules

Rules-based authorization decisioning helps reduce fraud, increase policy application, and improve user experiences. However, building a rules engine is time-intensive and challenging, and existing rules engines are constrained by the poor quality of the underlying data. Merchant-based rules are especially hard to create given the lack of consistent identifiers for merchants and the ever changing nature of transaction data -- historically, creating them meant relying on regex or making and constantly updating lists of MIDS.

Spade's transaction rules endpoints make it easy for you to make the best transaction authorization decisions – whether creating new engines or incorporating data into existing ones. You can use our endpoints to quickly and easily search our extensive merchant database ahead of authorization time, and block or allow card activity based on merchant, spend categories, and more.

As is the case for any merchant database, a small percentage of Spade's merchant IDs can change over time due to real-world changes (e.g. mergers and acquisitions) as well as general improvements. However, Spade's transaction rules system handles any shifts gracefully to ensure stability and robustness.

Creating your first transaction rule

For the purposes of this guide, we'll assume that we've issued a card card123 to user user123.

We can create a transaction rule of type allow to make sure that user123 can only use card123 at Walmart.

Step 1: Fetch the merchant ID (Walmart)

In order to create the desired transaction rule, we first need to grab Walmart's ID from Spade's merchant database. This ID is a proprietary Spade identifier that is consistent across all locations of a merchant. We'll use it to create the transaction rule.

Merchant IDs show up as counterparty IDs and/or third party IDs in enrichment responses. Whether a merchant shows up as a counterparty or a third party within a given enrichment is based on the role it is playing in the transaction.

We can use Spade's merchant search API to fetch the ID:

import requests

response = requests.get("https://east.sandbox.spade.com/merchants", params={"name": "walmart"}, headers={"X-Api-Key": "<Your API Key Here>"})
merchants = response.json()

print(merchants)

Sending the above request returns:

{
  "merchants": [
    {
      "id": "d730906b-f1a8-49f1-9939-f27390170a6d",
      "name": "Walmart",
      "similarity": 100.0,
      "logo": "https://v1.spadeapi.com/logos/verified/walmart.png?size=large",
      "website": "walmart.com"
    }
  ]
}

This endpoint can return multiple merchants ranked by similarity score, but in this case, there's only one and it's exactly the one we're looking for. Walmart's ID is: d730906b-f1a8-49f1-9939-f27390170a6d.

Step 2: Create the transaction rule

Now that we know Walmart's ID, we can create an allow transaction rule to ensure card123 can only be used at Walmart:

import requests

transaction_rule = {
    "userId": "user123",
    "cardId": "card123",
    "type": "allow",
    "condition": "counterparty_id == 'd730906b-f1a8-49f1-9939-f27390170a6d'",
    "parameters": {}
    },
}

response = requests.put("https://east.sandbox.spade.com/transaction-rules", json=transaction_rule, headers={"X-Api-Key": "<Your API Key Here>"})
transaction_rule_response = response.json()

print(transaction_rule_response)

Sending the above request returns:

{
  "userId": "user123",
  "cardId": "card123",
  "type": "allow",
  "condition": "counterparty_id == 'd730906b-f1a8-49f1-9939-f27390170a6d'",
  "parameters": {}
}

This response indicates that the transaction rule has been successfully created.

PUT = Create + Update

You'll use the same endpoint (PUT) to create and update transaction rules. This design simplifies frontend logic, since it means that you don't need to handle these cases separately.

Spade's API uses the userId and cardId as a unique key to determine which transaction rule to update (or whether to create a new transaction rule). As a result, there can only ever be one transaction rule for a given userId + cardId pair.

The status code provided in the response indicates whether the transaction rule was created or updated:

  • 200 - An existing transaction rule was updated
  • 201 - A new transaction rule was created

Step 3: Process transaction rule results at transaction time

Now that you've created the transaction rule, whenever user123 uses card123 and you send their transactions to Spade for enrichment, Spade will evaluate the transaction rule against the top matched counterparty, the top third party, and other enrichment outputs, and it will return the result of the transaction rule evaluation in the enrichment response.

For instance, let's say user123 swipes card123 at the Walmart Supercenter on 1590 Dunlawton Ave in Port Orange, Florida, and you send the transaction to Spade for enrichment:

import requests

raw_transaction = {
  "occurredAt": "2023-03-14T01:42:00Z",
  "acquirerId": "123456789",
  "merchantName": "SQ*WMSUPERCENTER#582",
  "transactionId": "166c5ad8-8a94-4964-a659-03cdb64525f2",
  # The userId must be correct in order for transaction rules to work
  "userId": "user123",
  # The cardId must be correct in order for transaction rules to work
  "cardId": "card123",
  "categoryCode": "5469",
  "categoryType": "MCC",
  "amount": "42.00",
  "currencyCode": "USD",
  "location": {
    "city": "PORT ORANGE",
    "region": "FL",
    "country": "USA"
  }
}

response = requests.post("https://east.sandbox.spade.com/transactions/enrich", json=raw_transaction, headers={"X-Api-Key": "<Your API Key Here>"})
enriched_transaction = response.json()

print(enriched_transaction)

Sending the above request returns:

{
  "transactionInfo": {
    "type": "spending",
    "thirdParties": [
      {
        "id": "8fbe0c0b-e54a-35a8-b8ff-0d982c84fc55",
        "name": "Square",
        "type": "payment_processor",
        "logo": "https://static.v2.spadeapi.com/logos/8fbe0c0be54a35a8b8ff0d982c84fc55/light.png"
      }
    ],
    "spendingInfo": {
      "channel": {
        "value": "physical"
      }
    }
  },
  "counterparty": [
    {
      "id": "d730906b-f1a8-49f1-9939-f27390170a6d",
      "name": "Walmart",
      "legalName": "Walmart Inc",
      "industry": [
        {
          "id": "011-000-000-000",
          "name": "Retail",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        },
        {
          "id": "011-018-000-000",
          "name": "General Goods",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        },
        {
          "id": "011-018-002-000",
          "name": "Department Stores",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        }
      ],
      "matchScore": 93.6,
      "location": [
        {
          "id": "380e18b7-bf9e-3545-b27e-80e36301c540",
          "address": "1590 Dunlawton Ave",
          "city": "Port Orange",
          "region": "FL",
          "postalCode": "32127",
          "country": "USA",
          "phoneNumber": "+13867562711",
          "latitude": 29.116576,
          "longitude": -81.02062
        }
      ],
      "logo": "https://v1.spadeapi.com/logos/verified/walmart.png?size=large",
      "medianSpendPerTransaction": 22.77,
      "phoneNumber": "+18004386278",
      "website": "walmart.com"
    }
  ],
  "transactionRuleInfo": {
    "result": true,
    "type": "allow",
    "confidence": 100.0
  },
  "enrichmentId": "78ba8476-fbe8-48cc-bbd0-1756a0eef0e3"
}

Because this transaction occurred at a Walmart, the transaction rule evaluated to true.

We are currently working to add nuance to the confidence. At the moment, the confidence is just set to 100 whenever there is a transaction rule that applies to the current transaction.

What happens if the card is used at another merchant?

Let's say user123 swipes card123 at the Apple store at 4200 Conroy Road in Orlando, Florida. In this case, you would want to decline the transaction since the card is only meant to be used at Walmart. Let's send the transaction to Spade for enrichment:

import requests

raw_transaction = {
  "occurredAt": "2023-03-14T01:42:00Z",
  "acquirerId": "123456789",
  "merchantName": "APPLESTORER053",
  "transactionId": "166c5ad8-8a94-4964-a659-03cdb64525f2",
  # The userId must be correct in order for transaction rules to work
  "userId": "user123",
  # The cardId must be correct in order for transaction rules to work
  "cardId": "card123",
  "categoryCode": "5732",
  "categoryType": "MCC",
  "amount": "42.00",
  "currencyCode": "USD",
  "location": {
    "city": "PORT ORANGE",
    "region": "FL",
    "country": "USA"
  }
}

response = requests.post("https://east.sandbox.spade.com/transactions/enrich", json=raw_transaction, headers={"X-Api-Key": "<Your API Key Here>"})
enriched_transaction = response.json()

print(enriched_transaction)

Sending the above request returns:

{
  "transactionInfo": {
    "type": "spending",
    "thirdParties": [],
    "spendingInfo": {
      "channel": {
        "value": "physical"
      }
    }
  },
  "counterparty": [
    {
      "id": "2b838cce-6565-4632-a53e-efbd2fb4b083",
      "name": "Apple",
      "legalName": null,
      "industry": [
        {
          "id": "013-000-000-000",
          "name": "Technology",
          "icon": "https://static.v2.spadeapi.com/categories/3fced507d20e4ed9b326613fbdb2b850/light.png"
        }
      ],
      "matchScore": 92.3,
      "location": [
        {
          "id": "fe88b629-3a6c-3144-8963-ef9580f41d9d",
          "address": "4200 Conroy Road",
          "city": "Orlando",
          "region": "FL",
          "postalCode": "32839",
          "country": "USA",
          "phoneNumber": null,
          "latitude": null,
          "longitude": null
        }
      ],
      "logo": "https://v1.spadeapi.com/logos/verified/apple.png?size=large",
      "medianSpendPerTransaction": 7.99,
      "phoneNumber": null,
      "website": "apple.com"
    }
  ],
  "transactionRuleInfo": {
    "result": false,
    "type": "allow",
    "confidence": 100.0
  },
  "enrichmentId": "e5f58693-1a65-45a1-b442-26fafe7c81a8"
}

Because this transaction did not occur at a Walmart, the condition evaluated to false. Since the rule is an allow rule, this transaction should not be allowed.

What happens if an enrichment output is missing (e.g. if there isn't a counterparty match)?

When an enrichment output is missing, the condition will be evaluated as usual, with missing information counting as null.

For instance, let's take a quick look at a transaction that doesn't return a counterparty match.

import requests

raw_transaction = {
  "occurredAt": "2023-03-14T01:42:00Z",
  "acquirerId": "123456789",
  "merchantName": "MAD HATTER SPORTS CO",
  "transactionId": "166c5ad8-8a94-4964-a659-03cdb64525f2",
  "userId": "f841399e-d095-4e7f-b004-c39d7e3fa329",
  "categoryCode": "5469",
  "categoryType": "MCC",
  "amount": "42.00",
  "currencyCode": "USD",
  "location": {
    "city": "HOUSTON",
    "region": "TX",
    "country": "USA"
  }
}

response = requests.post("https://east.sandbox.spade.com/transactions/enrich", json=raw_transaction, headers={"X-Api-Key": "<Your API Key Here>"})
enriched_transaction = response.json()

print(enriched_transaction)

Sending the above request returns:

{
  "transactionInfo": {
    "type": "spending",
    "thirdParties": [],
    "spendingInfo": {
      "channel": {
        "value": null
      }
    }
  },
  "counterparty": [
    {
      "id": null,
      "name": " Hatter Hatter Sports Co",
      "legalName": "MAD HATTER SPORTS CO",
      "industry": [
        {
          "id": "011-000-000-000",
          "name": "Retail",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        },
        {
          "id": "011-013-000-000",
          "name": "Specialty Retail",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        },
        {
          "id": "011-013-019-000",
          "name": "Sporting Goods",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        }
      ],
      "matchScore": null,
      "location": [
        {
          "id": null,
          "address": null,
          "city": "Houston",
          "region": "TX",
          "postalCode": null,
          "country": "USA",
          "phoneNumber": null,
          "latitude": null,
          "longitude": null
        }
      ],
      "logo": null,
      "medianSpendPerTransaction": null,
      "phoneNumber": null,
      "website": null
    }
  ],
  "transactionRuleInfo": {
    "result": false,
    "type": "allow",
    "confidence": 100.0
  },
  "enrichmentId": "0ff549ef-afc2-490b-bf09-fcbef9b86915"
}

Because there was no counterparty match, counterparty_id was null and the condition (counterparty_id == 'd730906b-f1a8-49f1-9939-f27390170a6d') therefore evaluated to false. At enrichment time, you can then decide whether or not to allow such transactions based on your use case.

In the future, the transactionRuleInfo object may also indicate whether or not missing information affected the evaluation result (e.g. via an additional flag and/or via the confidence value).

Creating a more complex transaction rule

Let's say we want to issue a card card123 that has two restrictions on how it can be used:

  1. No digital purchases
  2. No purchases at Walmart or Apple

We can create a transaction rule of type block in order to achieve this functionality. For simplicity, we'll pass in the list of blocked counterparties as a parameter.

import requests

transaction_rule = {
    "userId": "user123",
    "cardId": "card123",
    "type": "block",
    "condition": "channel == 'digital' or counterparty_id in @blocked_counterparty_ids",
    "parameters": {
        "blocked_counterparty_ids": [
            "d730906b-f1a8-49f1-9939-f27390170a6d",
            "2b838cce-6565-4632-a53e-efbd2fb4b083"
        ]
    }
}

response = requests.put("https://east.sandbox.spade.com/transaction-rules", json=transaction_rule, headers={"X-Api-Key": "<Your API Key Here>"})
transaction_rule_response = response.json()

print(transaction_rule_response)

Sending the above request returns:

{
  "userId": "user123",
  "cardId": "card123",
  "type": "block",
  "condition": "channel == 'digital' or counterparty_id in @blocked_counterparty_ids",
  "parameters": {
    "blocked_counterparty_ids": [
      "d730906b-f1a8-49f1-9939-f27390170a6d",
      "2b838cce-6565-4632-a53e-efbd2fb4b083"
    ]
  }
}

Now, let's say that user123 swipes card123 at the Walmart Supercenter on 1590 Dunlawton Ave in Port Orange, Florida, and you send the transaction to Spade for enrichment:

import requests

raw_transaction = {
  "occurredAt": "2023-03-14T01:42:00Z",
  "acquirerId": "123456789",
  "merchantName": "SQ*WMSUPERCENTER#582",
  "transactionId": "166c5ad8-8a94-4964-a659-03cdb64525f2",
  # The userId must be correct in order for transaction rules to work
  "userId": "user123",
  # The cardId must be correct in order for transaction rules to work
  "cardId": "card123",
  "categoryCode": "5469",
  "categoryType": "MCC",
  "amount": "42.00",
  "currencyCode": "USD",
  "location": {
    "city": "PORT ORANGE",
    "region": "FL",
    "country": "USA"
  }
}

response = requests.post("https://east.sandbox.spade.com/transactions/enrich", json=raw_transaction, headers={"X-Api-Key": "<Your API Key Here>"})
enriched_transaction = response.json()

print(enriched_transaction)

Sending the above request returns:

{
  "transactionInfo": {
    "type": "spending",
    "thirdParties": [
      {
        "id": "8fbe0c0b-e54a-35a8-b8ff-0d982c84fc55",
        "name": "Square",
        "type": "payment_processor",
        "logo": "https://static.v2.spadeapi.com/logos/8fbe0c0be54a35a8b8ff0d982c84fc55/light.png"
      }
    ],
    "spendingInfo": {
      "channel": {
        "value": "physical"
      }
    }
  },
  "counterparty": [
    {
      "id": "d730906b-f1a8-49f1-9939-f27390170a6d",
      "name": "Walmart",
      "legalName": "Walmart Inc",
      "industry": [
        {
          "id": "011-000-000-000",
          "name": "Retail",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        },
        {
          "id": "011-018-000-000",
          "name": "General Goods",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        },
        {
          "id": "011-018-002-000",
          "name": "Department Stores",
          "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
        }
      ],
      "matchScore": 93.6,
      "location": [
        {
          "id": "380e18b7-bf9e-3545-b27e-80e36301c540",
          "address": "1590 Dunlawton Ave",
          "city": "Port Orange",
          "region": "FL",
          "postalCode": "32127",
          "country": "USA",
          "phoneNumber": "+13867562711",
          "latitude": 29.116576,
          "longitude": -81.02062
        }
      ],
      "logo": "https://v1.spadeapi.com/logos/verified/walmart.png?size=large",
      "medianSpendPerTransaction": 22.77,
      "phoneNumber": "+18004386278",
      "website": "walmart.com"
    }
  ],
  "transactionRuleInfo": {
    "result": true,
    "type": "block",
    "confidence": 100.0
  },
  "enrichmentId": "78ba8476-fbe8-48cc-bbd0-1756a0eef0e3"
}

Because this transaction occurred at a Walmart, the transaction rule evaluated to true.

The transaction rule would also have evaluated to true if the spending channel had been digital, regardless of the matched counterparty.

Remember that transaction rules are unique for a given userId + cardId pair. Thus, you cannot create separate transaction rules of type block and allow for a given userId + cardId.

Conclusion

Congratulations! You've now created your first transaction rules and learned how Spade's transaction rule system can be used to issue cards that can be used only at specific merchants.

Next steps

Merchant Search Guide

Modifying Existing Transaction Rules

Transaction Rules Language Guide

Transaction Rules FAQ