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

# Action triggers guide

> Register rules that trigger custom actions when transactions match your criteria

## Overview

Action triggers let you register rules that fire custom actions when enriched transactions match your criteria. When a match occurs, the enrichment response includes your custom `action` data — enabling real-time rewards, spending controls, and transaction routing without any post-processing.

Spade supports two types of action triggers:

<CardGroup cols={2}>
  <Card title="Merchant action triggers" icon="store" href="/reference/merchant-action-triggers-guide">
    Trigger actions when transactions match specific merchants. Supports location-level and corporation-level matching with up to 300,000 triggers, depending on scope.
  </Card>

  <Card title="Category action triggers" icon="tags" href="/reference/category-action-triggers-guide">
    Trigger actions when transactions match Spade categories such as Travel, Gambling, or Groceries. Up to 300 triggers per scope, processed synchronously.
  </Card>
</CardGroup>

## Shared concepts

### Scope hierarchy

Both trigger types support four registration scopes. Higher scopes cascade down to lower scopes:

| Scope | Applies to |
| - | - |
| **Account** | All transactions across your account |
| **Program** | All transactions where `programId` matches |
| **User** | All transactions for all cards belonging to a user |
| **Card** | Only transactions for a specific card |

When a transaction is enriched, Spade checks for matching triggers across all applicable scopes — card, user, program, then account. Multiple matches from different scopes can appear in a single response.

### What is a program?

A **program** is a freeform identifier you define — it requires no upfront configuration. You supply a `programId` string on your enrichment requests to group transactions however makes sense for your business (e.g., by card product, customer, or business line).

<Note>
  `programId`, `userId`, and `cardId` each have a maximum length of 512 characters.
</Note>

### Action types

The `action` object on each trigger is your custom JSON payload. The `type` field supports reserved values with special behavior:

| Type | Behavior |
| - | - |
| `BLOCK` | Adds `authRecommendation: "BLOCK"` to the action |
| `ALLOW_ONLY` | Adds `authRecommendation: "ALLOW"` when matched. When no `ALLOW_ONLY` trigger matches at that scope, each unmatched trigger is returned with `authRecommendation: "BLOCK"` — see [Resolving multiple actions](#resolving-multiple-actions) |
| `REWARD` | Passed through as-is |
| Any other value | Passed through as-is |

`BLOCK` and `ALLOW_ONLY` are the only action types that receive an `authRecommendation`. A single enrichment can return multiple actions carrying different `authRecommendation` values.

### Triggered actions in enrichment responses

Matched triggers appear in the `actions` array of the enrichment response. Each entry includes the trigger `id`, `type`, your custom `action` data, the `scope` it was registered at, and the `source` of the match. `source` is only populated for merchant triggers; category triggers always return `source: null`.

```json theme={null}
{
  "actions": [
    {
      "id": "trigger-1",
      "type": "merchant_trigger",
      "action": { "type": "REWARD", "rewardPercent": 5 },
      "scope": "account",
      "source": "counterparty"
    },
    {
      "id": "cat-trigger-1",
      "type": "category_trigger",
      "action": { "type": "REWARD", "rewardPercent": 3 },
      "scope": "account",
      "source": null,
      "categoryId": "020-001-000-000",
      "categoryName": "Travel"
    }
  ]
}
```

The `actions` field is `null` when no triggers match, and is omitted entirely if your account does not have the actions feature enabled.

## Resolving multiple actions

A single enrichment can match several triggers, and those triggers can carry differing `authRecommendation` values. Spade returns every action that resolved — your authorization logic decides which recommendation to enforce.

The scenarios below cover common cases where `ALLOW_ONLY` or `BLOCK` triggers come into play, and what the response looks like in each.

### Within a given scope, allowlists act as an `OR`

Multiple `ALLOW_ONLY` triggers at the same scope form an allowlist. Once any one of them matches, the allowlist is satisfied and any unmatched `ALLOW_ONLY` triggers at that scope are omitted from the response.

Given two account-scoped `ALLOW_ONLY` triggers — one for Starbucks, one for Dunkin' — a Starbucks transaction returns one action:

```json theme={null}
{
  "actions": [
    {
      "id": "allow-starbucks",
      "type": "merchant_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "ALLOW" },
      "scope": "account",
      "source": "counterparty"
    }
  ]
}
```

The Dunkin' trigger is not returned with an `authRecommendation: "BLOCK"`, because the allowlist it belongs to is satisfied.

### An unsatisfied allowlist returns one BLOCK per trigger

When no `ALLOW_ONLY` trigger matches at a scope, every unmatched trigger at that scope is returned with `authRecommendation: "BLOCK"`. The same two triggers, against a transaction that matches to a counterparty besides Starbucks or Dunkin':

```json theme={null}
{
  "actions": [
    {
      "id": "allow-starbucks",
      "type": "merchant_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "BLOCK" },
      "scope": "account",
      "source": null
    },
    {
      "id": "allow-dunkin",
      "type": "merchant_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "BLOCK" },
      "scope": "account",
      "source": null
    }
  ]
}
```

### Allowlists apply within a scope, not across scopes

Triggers at different scopes are evaluated independently, so an `ALLOW` from one scope and a `BLOCK` from another can appear in the same response.

Given an `ALLOW_ONLY` on Starbucks at card scope and an `ALLOW_ONLY` on Dunkin' at account scope, a Starbucks transaction satisfies the card-scope allowlist but leaves the account-scope allowlist unmatched:

```json theme={null}
{
  "actions": [
    {
      "id": "allow-starbucks",
      "type": "merchant_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "ALLOW" },
      "scope": "card",
      "source": "counterparty"
    },
    {
      "id": "allow-dunkin",
      "type": "merchant_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "BLOCK" },
      "scope": "account",
      "source": null
    }
  ]
}
```

### Registered BLOCK triggers are never omitted

A registered `BLOCK` trigger will **always** return when it matches. When an enrichment matches both an `ALLOW_ONLY` and a `BLOCK`, both actions are returned — both at the same scope and across scopes.

The three cases below all produce two actions.

**A parent category allowed, a child category blocked.** An `ALLOW_ONLY` on Food and Drink and a `BLOCK` on Fast Food, both at account scope. A Fast Food transaction matches both:

```json theme={null}
{
  "actions": [
    {
      "id": "allow-food-and-drink",
      "type": "category_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "ALLOW" },
      "scope": "account",
      "source": null,
      "categoryId": "005-000-000-000",
      "categoryName": "Food and Drink"
    },
    {
      "id": "block-fast-food",
      "type": "category_trigger",
      "action": { "type": "BLOCK", "authRecommendation": "BLOCK" },
      "scope": "account",
      "source": null,
      "categoryId": "005-006-002-000",
      "categoryName": "Fast Food"
    }
  ]
}
```

**A corporation allowed, one of its locations blocked.** A corporation-level `ALLOW_ONLY` and a location-level `BLOCK`, both at account scope. A transaction enrichment matches to that corporation and location, so both actions are returned:

```json theme={null}
{
  "actions": [
    {
      "id": "allow-corp-starbucks",
      "type": "merchant_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "ALLOW" },
      "scope": "account",
      "source": "counterparty"
    },
    {
      "id": "block-starbucks-airport-store",
      "type": "merchant_trigger",
      "action": { "type": "BLOCK", "authRecommendation": "BLOCK" },
      "scope": "account",
      "source": "counterparty"
    }
  ]
}
```

**A counterparty allowed, a third party blocked.** An `ALLOW_ONLY` on a counterparty and a `BLOCK` on a third party, both at account scope. If a transaction enrichment matches to both the counterparty and third party, both actions are returned.

For example, if the request contains the descriptor `DOORDASH* MCDONALDS`, the transaction enrichment matches:

```json theme={null}
{
  "actions": [
    {
      "id": "allow-mcdonalds",
      "type": "merchant_trigger",
      "action": { "type": "ALLOW_ONLY", "authRecommendation": "ALLOW" },
      "scope": "account",
      "source": "counterparty"
    },
    {
      "id": "block-doordash",
      "type": "merchant_trigger",
      "action": { "type": "BLOCK", "authRecommendation": "BLOCK" },
      "scope": "account",
      "source": "third_party"
    }
  ]
}
```

<Note>
  The right precedence for handling multiple `authRecommendation` triggers depends on what you registered the triggers for. Reach out to your support contact if you would like to talk through your use case.
</Note>

## Unresolved merchants

When Spade cannot match a transaction to a counterparty or a third party, merchant and category triggers behave differently:

| Trigger type | Behavior with no counterparty or third-party match |
| - | - |
| **Merchant** | No merchant actions are returned. |
| **Category** | Category triggers are evaluated as long as the enrichment contains enough information for us to identify the [industry](/reference/concepts#industry). |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.