ⓘ 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]](mailto:[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.
⚠ 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
andcardId
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 givenuserId
+cardId
pair.The status code provided in the response indicates whether the transaction rule was created or updated:
200
- An existing transaction rule was updated201
- 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, theconfidence
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 theconfidence
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:
- No digital purchases
- 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 typeblock
andallow
for a givenuserId
+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
Modifying Existing Transaction Rules