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

# Category action triggers guide

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

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

## Overview

Category action triggers let you register rules that fire custom actions when transactions are enriched into specific Spade categories. Triggers match on Spade's category taxonomy rather than individual merchants, so a single trigger can cover every merchant in a category — to target specific merchants instead, see [merchant action triggers](/reference/merchant-action-triggers-guide).

**Common use cases:**

* **Category-based rewards:** 3x points on all Travel transactions
* **Spending controls:** Block all Gambling transactions
* **Category allowlists:** Only allow transactions in approved categories (e.g., Fuel, Office Supplies)

## Quick Start

1. **Discover categories** — Call `GET /categories` to get the list of available category IDs
2. **Register triggers** — Use the PUT endpoint with your category trigger configuration
3. **Enrich transactions** — Matched triggers appear in the `actions` field of the enrichment response
4. *(Optional)* **Update triggers incrementally** — Use the PATCH endpoint to [add or remove individual triggers](#updating-triggers) without re-registering the full list

## Discovering categories

Use the `GET /categories` endpoint to retrieve the full list of Spade categories. Each category has an `id` that you'll use as the `categoryId` when registering triggers.

```bash theme={null}
curl --request GET \
  --url https://east.api.spade.com/categories \
  --header 'X-Api-Key: YOUR_API_KEY'
```

The response includes category IDs and names:

```json theme={null}
[
  {
    "id": "020-001-000-000",
    "name": "Travel",
    "icon": "https://static.v2.spadeapi.com/categories/..."
  },
  {
    "id": "019-001-000-000",
    "name": "Gambling",
    "icon": "https://static.v2.spadeapi.com/categories/..."
  }
]
```

Use the `id` value as the `categoryId` in your trigger registrations.

## Registration Scopes

Category 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                                                  | Limit        |
| ------- | --------------------------------------------------------- | ------------ |
| Account | `/category-action-triggers`                               | 300 triggers |
| Program | `/programs/{programId}/category-action-triggers`          | 300 triggers |
| User    | `/users/{userId}/category-action-triggers`                | 300 triggers |
| Card    | `/users/{userId}/cards/{cardId}/category-action-triggers` | 300 triggers |

All registrations are processed **synchronously** — there is no batch processing. Status will be `succeeded` immediately upon a successful request.

## Registering category action triggers

Use the PUT endpoint to register category action triggers.

<Warning>
  **PUT replaces your entire trigger list.** Every PUT request overwrites all previously registered triggers at that scope. If you need to add or remove a few triggers, use the PATCH endpoint instead.

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

### Request body

Each trigger requires:

* `id` — Your unique identifier for this trigger (max 512 characters)
* `categoryId` — A valid Spade category ID (use `GET /categories` to discover valid IDs)
* `action` — Your custom action data (same semantics as merchant triggers)

<Note>
  You do not need to provide a `categoryName` — it is resolved from the system during enrichment and returned in the response.
</Note>

### Example: REWARD trigger on Travel

<CodeGroup>
  ```bash bash theme={null}
  curl --request PUT \
    --url https://east.api.spade.com/category-action-triggers \
    --header 'Content-Type: application/json' \
    --header 'X-Api-Key: YOUR_API_KEY' \
    --data '{
      "categoryTriggers": [
        {
          "id": "travel-reward",
          "categoryId": "020-001-000-000",
          "action": {
            "type": "REWARD",
            "rewardPercent": 3,
            "offerId": "travel-3x-promo"
          }
        }
      ]
    }'
  ```

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

  response = requests.put(
      "https://east.api.spade.com/category-action-triggers",
      headers={
          "Content-Type": "application/json",
          "X-Api-Key": "YOUR_API_KEY"
      },
      json={
          "categoryTriggers": [
              {
                  "id": "travel-reward",
                  "categoryId": "020-001-000-000",
                  "action": {
                      "type": "REWARD",
                      "rewardPercent": 3,
                      "offerId": "travel-3x-promo"
                  }
              }
          ]
      }
  )

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

### Example: BLOCK trigger on Gambling

<CodeGroup>
  ```bash bash theme={null}
  curl --request PUT \
    --url https://east.api.spade.com/category-action-triggers \
    --header 'Content-Type: application/json' \
    --header 'X-Api-Key: YOUR_API_KEY' \
    --data '{
      "categoryTriggers": [
        {
          "id": "block-gambling",
          "categoryId": "019-001-000-000",
          "action": {
            "type": "BLOCK",
            "reason": "Gambling transactions not permitted"
          }
        }
      ]
    }'
  ```

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

  response = requests.put(
      "https://east.api.spade.com/category-action-triggers",
      headers={
          "Content-Type": "application/json",
          "X-Api-Key": "YOUR_API_KEY"
      },
      json={
          "categoryTriggers": [
              {
                  "id": "block-gambling",
                  "categoryId": "019-001-000-000",
                  "action": {
                      "type": "BLOCK",
                      "reason": "Gambling transactions not permitted"
                  }
              }
          ]
      }
  )

  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 category action trigger registration.

### Status Values

| Status      | Description                                       |
| ----------- | ------------------------------------------------- |
| `succeeded` | Registration complete, triggers are now active    |
| `failed`    | Registration failed, check your request and retry |

Since category trigger registrations are processed synchronously, the status is `succeeded` or `failed` immediately after a PUT or PATCH request.

<Warning>
  **Important:** Triggers are only applied to enrichment responses when the status is `succeeded`.
</Warning>

### Example: Check Status

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

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

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

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

### Response

```json theme={null}
{
  "status": "succeeded",
  "version": 1,
  "categoryTriggers": [
    {
      "id": "travel-reward",
      "action": { "type": "REWARD", "rewardPercent": 3, "offerId": "travel-3x-promo" }
    },
    {
      "id": "block-gambling",
      "action": { "type": "BLOCK", "reason": "Gambling transactions not permitted" }
    }
  ],
  "totalCount": 2
}
```

## Updating Triggers

Use the PATCH endpoint to incrementally add or remove triggers without replacing the entire list.

### Adding triggers

Use `operation: "add"` with `categoryTriggers` to register new triggers or update existing ones. If a trigger ID already exists, its registration is replaced (upsert behavior).

```bash theme={null}
curl --request PATCH \
  --url https://east.api.spade.com/category-action-triggers \
  --header 'Content-Type: application/json' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --data '{
    "operation": "add",
    "categoryTriggers": [
      {
        "id": "groceries-reward",
        "categoryId": "005-001-000-000",
        "action": {
          "type": "REWARD",
          "rewardPercent": 2,
          "offerId": "groceries-2x"
        }
      }
    ]
  }'
```

### Removing triggers

Use `operation: "remove"` with `triggerIds` to deactivate triggers. Trigger IDs that are not currently active are silently ignored (idempotent).

```bash theme={null}
curl --request PATCH \
  --url https://east.api.spade.com/category-action-triggers \
  --header 'Content-Type: application/json' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --data '{
    "operation": "remove",
    "triggerIds": ["block-gambling"]
  }'
```

## Clearing Triggers

Use the DELETE endpoint to clear all category 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 Account-Scoped Triggers

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

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

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

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

## ALLOW\_ONLY Inverse Semantics

When category triggers use `ALLOW_ONLY`, the behavior inverts for non-matching transactions:

* **Transaction in a registered category:** `authRecommendation: "ALLOW"`
* **Transaction in any other category:** `authRecommendation: "BLOCK"`, `source: null`

This allows you to create category allowlists where only transactions in approved categories are permitted.

### Example: Allow only Fuel and Office Supplies

```bash theme={null}
curl --request PUT \
  --url https://east.api.spade.com/category-action-triggers \
  --header 'Content-Type: application/json' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --data '{
    "categoryTriggers": [
      {
        "id": "allow-fuel",
        "categoryId": "016-001-000-000",
        "action": { "type": "ALLOW_ONLY" }
      },
      {
        "id": "allow-office",
        "categoryId": "010-001-000-000",
        "action": { "type": "ALLOW_ONLY" }
      }
    ]
  }'
```

## Receiving Triggered Actions

When a transaction matches a category trigger, the enrichment response includes the trigger in the `actions` array with `type: "category_trigger"`. Category triggers include additional `categoryId` and `categoryName` fields.

### Example: Enrichment Response with Category Trigger

```json theme={null}
{
  "enrichmentId": "d1f7490c-719e-4c75-8bfa-535c8a32b936",
  "transactionInfo": {
    "type": "spending",
    "display": {
      "name": "United Airlines",
      "categoryName": "Travel"
    }
  },
  "actions": [
    {
      "id": "travel-reward",
      "type": "category_trigger",
      "action": {
        "type": "REWARD",
        "rewardPercent": 3,
        "offerId": "travel-3x-promo"
      },
      "scope": "account",
      "source": "counterparty",
      "categoryId": "020-001-000-000",
      "categoryName": "Travel"
    }
  ]
}
```

### Example: Both Merchant and Category Triggers

When a transaction matches both merchant and category triggers, all matched triggers appear in the `actions` array:

```json theme={null}
{
  "enrichmentId": "a2b3c4d5-e6f7-8901-abcd-ef2345678901",
  "transactionInfo": {
    "type": "spending",
    "display": {
      "name": "Starbucks",
      "categoryName": "Coffee Shop"
    }
  },
  "actions": [
    {
      "id": "starbucks-reward",
      "type": "merchant_trigger",
      "action": {
        "type": "REWARD",
        "rewardPercent": 5,
        "offerId": "summer-2024-promo"
      },
      "scope": "account",
      "source": "counterparty"
    },
    {
      "id": "dining-reward",
      "type": "category_trigger",
      "action": {
        "type": "REWARD",
        "rewardPercent": 2,
        "offerId": "dining-2x"
      },
      "scope": "program",
      "source": "counterparty",
      "categoryId": "008-001-000-000",
      "categoryName": "Coffee Shop"
    }
  ]
}
```

<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 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**. Reserved keywords (`BLOCK`, `ALLOW_ONLY`) have special authorization behavior. `REWARD` and all other values are passed through as-is.

### Scope Selection

* Use **account scope** for triggers that apply to all users (e.g., company-wide category restrictions)
* Use **program scope** for triggers scoped to a specific program (e.g., different reward tiers per card product)
* Use **user scope** for user-specific preferences
* Use **card scope** for card-specific rules (e.g., a dedicated rewards card with higher multipliers)

### Choosing PUT vs. PATCH

* Use **PUT** when replacing your full set of triggers (e.g., initial setup or a periodic full sync)
* Use **PATCH** when adding or removing a small number of triggers relative to your total list

### Error Handling

* Implement retry logic for 5xx errors
* Handle 409 Conflict by waiting for the in-progress registration to complete
* Validate your trigger data before submission to avoid 400 errors
