> ## 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.

# Merchant action triggers guide

> Register merchant action triggers and receive triggered actions in your enrichment responses

<Note>
  This guide covers merchant action triggers. For an overview of all trigger types, see the [Action triggers guide](/reference/action-triggers-guide). For category-based triggers, see the [Category action triggers guide](/reference/category-action-triggers-guide).
</Note>

## Overview

Spade's merchant action triggers allow you to register rules that trigger custom actions when transactions match specific merchants. When a transaction matches one of your registered triggers, the enrichment response includes your custom `action` data.

**Common use cases:**

* **Merchant rewards:** Identify transactions at specific merchants to trigger reward multipliers
* **Transaction flagging:** Flag transactions for review based on a merchant
* **Program-scoped triggers:** Register different trigger rules per program — for example, an issuer with both corporate and consumer card programs can register distinct merchant action triggers for each program without duplicating them across every user or card

## Quick Start

1. **Ensure the actions feature is enabled** - Contact your Spade representative if you need access
2. **Register your triggers** using the PUT endpoint with your trigger configuration
3. **Wait for registration to complete** - Check the GET endpoint until status is `succeeded`
4. **Enrich transactions** - Matched triggers appear in the `actions` field

## Registration Scopes

Merchant action triggers can be registered at four scopes. See the [Action triggers guide](/reference/action-triggers-guide#scope-hierarchy) for how scope inheritance works.

| Scope   | Endpoint                                                  | Applies To                                                     | Per-request limit | Total cap per scope |
| ------- | --------------------------------------------------------- | -------------------------------------------------------------- | ----------------- | ------------------- |
| Account | `/merchant-action-triggers`                               | All transactions for all users in your account                 | 100,000 triggers  | 300,000 triggers    |
| Program | `/programs/{programId}/merchant-action-triggers`          | All enrichment requests where `programId` matches this program | 100,000 triggers  | 300,000 triggers    |
| User    | `/users/{userId}/merchant-action-triggers`                | All transactions for all cards belonging to that user          | 100 triggers      | 100 triggers        |
| Card    | `/users/{userId}/cards/{cardId}/merchant-action-triggers` | Only transactions for that specific card                       | 100 triggers      | 100 triggers        |

<Note>
  At **account and program scope**, the **per-request limit** (100,000 triggers) and the **total cap per scope** (300,000 triggers) are different limits: a single request may carry up to 100,000 triggers, but a scope can hold up to 300,000 active triggers in total. To register more than 100,000 triggers, split them into ≤100,000 batches and send them sequentially — see [Registering large trigger sets](#registering-large-trigger-sets). User and card scopes are capped at 100 triggers total.
</Note>

**Scope inheritance:** When a transaction is enriched, Spade checks for matching triggers across all applicable scopes. For example, a transaction with a `programId`, `userId`, and `cardId` will check:

1. Card-level triggers (for that card)
2. User-level triggers (for that user)
3. Program-level triggers (for that program)
4. Account-level triggers (for your account)

Multiple matches from different scopes can be returned in a single enrichment 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>

## Registering merchant action triggers

Use the PUT endpoint to register merchant action triggers for action matching.

<Warning>
  **PUT replaces your entire trigger list.** Every PUT request overwrites all previously registered triggers at that scope. If you have 50,000 registered merchants and need to add or remove a handful, use the PATCH endpoint instead — sending the full list via PUT to change a few entries is inefficient and unnecessarily slow.

  See the <a href="/api-reference/merchant-action-triggers/incremental-trigger-operations-at-account-scope" target="_blank">API reference</a> for PATCH endpoint details.
</Warning>

<Note>
  **Two matching modes for merchant action triggers:**

  1. **Location-level:** (default) Applies triggers to a specific physical location (e.g. one specific Costco location)
  2. **Corporation-level:** Applies triggers to a whole brand (e.g. Costco in general)
</Note>

### Example: Register Merchant Action Triggers at the Account Scope

<Info>
  The example below registers triggers at the **account scope**. To register at other scopes, replace the URL path accordingly — for example, use `/programs/{programId}/merchant-action-triggers` for program scope.

  See the <a href="/api-reference/merchant-action-triggers/register-merchant-triggers-at-account-scope" target="_blank">API reference</a> for full endpoint details.
</Info>

<CodeGroup>
  ```bash bash theme={null}
  curl --request PUT \
    --url https://east.sandbox.spade.com/merchant-action-triggers \
    --header 'Content-Type: application/json' \
    --header 'X-Api-Key: YOUR_API_KEY' \
    --data '{
      "merchantTriggers": [
        {
          "id": "trigger-1",
          "merchantName": "Starbucks",
          "website": "https://www.starbucks.com",
          "level": "corporation",
          "action": {
            "type": "REWARD",
            "rewardPercent": 5,
            "offerId": "summer-2024-promo"
          }
        },
        {
          "id": "trigger-2",
          "merchantName": "Target",
          "address": "1000 Nicollet Mall",
          "city": "Minneapolis",
          "region": "MN",
          "country": "USA",
          "postalCode": "55403",
          "level": "location",
          "action": {
            "type": "REWARD",
            "rewardPercent": 3,
            "offerId": "target-cashback"
          }
        }
      ]
    }'
  ```

  ```python python theme={null}
  import requests

  response = requests.put(
      "https://east.sandbox.spade.com/merchant-action-triggers",
      headers={
          "Content-Type": "application/json",
          "X-Api-Key": "YOUR_API_KEY"
      },
      json={
          "merchantTriggers": [
              {
                  "id": "trigger-1",
                  "merchantName": "Starbucks",
                  "website": "https://www.starbucks.com",
                  "level": "corporation",
                  "action": {
                      "type": "REWARD",
                      "rewardPercent": 5,
                      "offerId": "summer-2024-promo"
                  }
              },
              {
                  "id": "trigger-2",
                  "merchantName": "Target",
                  "address": "1000 Nicollet Mall",
                  "city": "Minneapolis",
                  "region": "MN",
                  "country": "USA",
                  "postalCode": "55403",
                  "level": "location",
                  "action": {
                      "type": "REWARD",
                      "rewardPercent": 3,
                      "offerId": "target-cashback"
                  }
              }
          ]
      }
  )

  print(response.json())
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "status": "succeeded",
  "version": 1
}
```

## Checking Registration Status

Use the GET endpoint to check the status of your action trigger registration.

### Status Values

| Status      | Description                                               |
| ----------- | --------------------------------------------------------- |
| `pending`   | Registration received, processing not yet started         |
| `running`   | Registration is being processed (for batch registrations) |
| `succeeded` | Registration complete, triggers are now active            |
| `failed`    | Registration failed, check your request and retry         |

<Warning>
  **Important:** Triggers are only applied to enrichment responses when the status is `succeeded`. Transactions enriched while status is `pending` or `running` will NOT include the new triggers.
</Warning>

### Example: Check Status

<CodeGroup>
  ```bash bash theme={null}
  curl --request GET \
    --url https://east.sandbox.spade.com/merchant-action-triggers \
    --header 'X-Api-Key: YOUR_API_KEY'
  ```

  ```python python theme={null}
  import requests

  response = requests.get(
      "https://east.sandbox.spade.com/merchant-action-triggers",
      headers={"X-Api-Key": "YOUR_API_KEY"}
  )

  print(response.json())
  ```
</CodeGroup>

## Batch Registrations (Account and Program Scope)

Account-scope and program-scope registrations with more than 100 merchant action triggers are processed asynchronously. The PUT request returns immediately with `status: "pending"`, then progresses through `running` → `succeeded` (or `failed`). Poll the GET endpoint to monitor progress.

<Warning>
  **Per-request limits:** User-scope and card-scope registrations are limited to **100 merchant action triggers**. Requests exceeding this limit will receive a `400` error. This does not limit the total number of users or cards you can register triggers for — you can make as many requests as needed across different scopes.
</Warning>

<Info>
  For large account-scope or program-scope trigger lists, we recommend implementing a polling loop with exponential backoff. Small registrations complete within a few minutes, but large batches (tens of thousands of triggers) can take **well over an hour** — size your polling timeout accordingly rather than assuming a few minutes.
</Info>

### Example: Polling for Completion

```python python theme={null}
import requests
import time

def wait_for_trigger_success(api_key, max_attempts=145, base_delay=2):
    """Poll until action trigger status is succeeded or failed.

    Large batch registrations can take well over an hour, so max_attempts is
    generous (~2.5 hours at the capped poll interval). Backoff is capped so we
    keep polling politely rather than hammering the endpoint.
    """
    for attempt in range(max_attempts):
        response = requests.get(
            "https://east.sandbox.spade.com/merchant-action-triggers",
            headers={"X-Api-Key": api_key}
        )

        data = response.json()
        status = data.get("status")

        if status == "succeeded":
            print(f"Registration complete! Version: {data.get('version')}")
            return True
        elif status == "failed":
            print("Registration failed. Please check your request and retry.")
            return False
        else:
            # Exponential backoff, capped to poll politely
            delay = base_delay * (2 ** min(attempt, 5))
            print(f"Status: {status}. Checking again in {delay}s...")
            time.sleep(delay)

    print("Timed out waiting for registration to complete")
    return False
```

## Registering large trigger sets

There are two distinct limits at account and program scope:

* **Per-request limit — 100,000 triggers.** A single PUT or PATCH `add` request may carry at most 100,000 triggers. Requests exceeding this return a `400` error.
* **Total cap per scope — 300,000 triggers.** A scope can hold up to 300,000 active triggers in total across all requests. A PATCH `add` whose net-new merchants would push the scope past 300,000 returns a `400` error (PUT cannot exceed the cap, since it is itself limited to 100,000 triggers per request).

Because the per-request limit (100,000) is lower than the total cap (300,000), registering a large set requires splitting it into **batches of up to 100,000 triggers** and sending them with sequential PATCH `add` operations.

<Warning>
  **One registration at a time per scope.** Only one registration or operation can be in progress per scope. While a batch is `pending` or `running`, any other PUT or PATCH to the same scope returns a `409` Conflict. Batches must therefore be sent **sequentially** — wait for each batch to reach `succeeded` before submitting the next.
</Warning>

**Recommended workflow:**

1. Split your full trigger list into chunks of **≤100,000 triggers**.
2. Submit the first chunk:
   * Use **PUT** if this registration should replace whatever is currently in the scope — a clean full sync. This is the safe default when you want the scope to contain exactly the set you're about to register.
   * Use **PATCH `add`** if you're adding to triggers that already exist and want to keep them.
3. Poll the GET endpoint until status is `succeeded`.
4. Submit each remaining chunk with PATCH `add` (**not** PUT) and repeat until all chunks are registered.

<Note>
  Only the **first** batch may use PUT, and only when you want to replace the scope. Every subsequent batch must use PATCH `add`: PUT replaces the entire trigger list, so using it for a later batch would discard the triggers registered in earlier batches. PATCH `add` is additive — each request only processes the merchants it carries and leaves existing triggers untouched.
</Note>

## Clearing Triggers

Use the DELETE endpoint to clear all triggers at a given scope.

<Note>
  DELETE creates a new version with an empty trigger list. The version number is incremented, not reset.
</Note>

### Example: Clear an Account-Scoped Trigger

<CodeGroup>
  ```bash bash theme={null}
  curl --request DELETE \
    --url https://east.sandbox.spade.com/merchant-action-triggers \
    --header 'X-Api-Key: YOUR_API_KEY'
  ```

  ```python python theme={null}
  import requests

  response = requests.delete(
      "https://east.sandbox.spade.com/merchant-action-triggers",
      headers={"X-Api-Key": "YOUR_API_KEY"}
  )

  # Returns 204 No Content on success
  print(f"Status code: {response.status_code}")
  ```
</CodeGroup>

## Receiving Triggered Actions

When a transaction matches one or more of your registered triggers, the enrichment response includes the `actions` field.

### Triggered Action Response Fields

| Field    | Description                                                                                                                                                                                                                                                |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`     | Your trigger ID (as provided during registration)                                                                                                                                                                                                          |
| `type`   | The type of trigger that matched (e.g., `merchant_trigger`)                                                                                                                                                                                                |
| `action` | Your custom action data from registration                                                                                                                                                                                                                  |
| `scope`  | The scope at which this trigger was registered (`account`, `program`, `user`, or `card`)                                                                                                                                                                   |
| `source` | Whether the action was triggered by the matched `counterparty` or `third_party`. Set to `null` when an `ALLOW_ONLY` action trigger is set up whose condition the current transaction did not meet (the `authRecommendation` in this case will be `BLOCK`). |

### Example: Enrichment Response with Triggered Actions

```json theme={null}
{
  "enrichmentId": "d1f7490c-719e-4c75-8bfa-535c8a32b936",
  "transactionInfo": {
    "type": "spending",
    "display": {
      "name": "Starbucks",
      "categoryName": "Coffee Shop"
    }
  },
  "counterparty": [
    {
      "id": "abc123-def456",
      "name": "Starbucks",
      "matchScore": 95.2
    }
  ],
  "actions": [
    {
      "id": "trigger-1",
      "type": "merchant_trigger",
      "action": {
        "type": "REWARD",
        "rewardPercent": 5,
        "offerId": "summer-2024-promo"
      },
      "scope": "account",
      "source": "counterparty"
    }
  ]
}
```

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

## Best Practices

### Unique Trigger IDs

Trigger IDs must be unique **within each scope**. A scope is defined by the URL path you register against (account, program, user, or card level).

* **Within a single request:** All trigger IDs must be unique. Duplicate IDs in the same request will return a `400` error.
* **Across requests in the same scope:** Submitting a trigger with an ID that already exists in that scope will replace the previous registration (upsert). This applies to both `PUT` (full replacement) and `PATCH add` operations.
* **Across different scopes:** The same trigger ID can be used independently in different scopes. For example, `"starbucks-reward"` can exist in both a user-level and a program-level scope without conflict.

### Action Structure

Design your `action` schema upfront. This is your custom data - use it to store reward amounts, offer IDs, or any information you need when processing matched transactions.

The `type` field is **required**. There is currently one reserved keyword:

* `REWARD` - For reward-based actions (e.g., cashback, points multipliers)

Spade will execute pre-defined behavior on any reserved type.

You may also use custom type values for your own categorization needs.

### Scope Selection

* Use **account scope** for triggers that apply to all users (e.g., card-linked offer partners)
* Use **program scope** for triggers scoped to a specific program (e.g., a subset of customers or product line)
* Use **user scope** for user-specific preferences
* Use **card scope** for card-specific rules (e.g., category-specific rewards cards)
* Triggers cascade downward — for example, an account-level trigger will match transactions for all programs, users, and cards. A program-level trigger will match all users and cards that send enrichments with that `programId`, but won't affect other programs

### Choosing PUT vs. PATCH

* Use **PUT** when replacing your full set of triggers (e.g., initial setup or a periodic full sync)
* Use **PATCH** to add or remove merchants relative to your total trigger list — including registering more than 100,000 triggers via sequential `add` batches (see [Registering large trigger sets](#registering-large-trigger-sets))

### Error Handling

* Implement retry logic for 5xx errors
* Handle 409 Conflict by waiting for the in-progress registration to complete — only one registration can be in progress per scope, so send batched registrations sequentially
* Validate your trigger data before submission to avoid 400 errors
