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

# Batch enrichment guide

> Enrich many transactions at once

If you don't need realtime enrichment or are providing historical transaction data when first integrating with Spade you can use our batch endpoints instead of sending transactions individually.

Batch enrichment allows you to process up to `50,000` transactions in a single request and retrieve the enriched results once processing is complete. Below you'll find the data types supported and their corresponding endpoints:

**Card Enrichment**

* [/batches/transactions/cards/enrich](/api-reference/card-enrichment/enrich-a-batch-of-card-transactions)
* [/batches/transactions/cards/enrich/parse](/api-reference/card-enrichment/enrich-a-batch-of-card-transactions-with-de43-data)

**Transfers enrichment**

* [/batches/transactions/transfers/enrich](/api-reference/transfer-enrichment/enrich-a-batch-of-transfers)

**Universal data enrichment**

* [/batches/transactions/universal/enrich](/api-reference/universal-enrichment/enrich-a-batch-of-universal-transactions)

<Note>
  Each batch endpoint also supports **microbatching** for use cases where you need to enrich a small number of records quickly. See the [Microbatch enrichment guide](/reference/microbatch-enrichment-guide) for details.

  If you have a large number of records and need to optimize for throughput, the async batch enrichment system detailed below will provide better performance.
</Note>

## Getting Started

Let's walk through the process of enriching a batch of transactions:

### Preparing your batch

Depending on the type of data you're sending different fields may be required but regardless of whether you're sending card, transfer or aggregated data we recommend the following:

**Using unique Transaction IDs**

Ensure each transaction in your batch has a unique `transactionId`. Results are not guaranteed to be returned in the order in which they were submitted, so you can use the `transactionId` to correlate transactions in the results with the original requests. Duplicate IDs will cause the batch enrichment request to fail.

**Sending data in chronological order**

We recommend sending your oldest transactions first as this can have an impact on the insights we can provide on transaction data such as recurrence.

**Using Custom attributes**

If you have additional data elements that you would like to associate to any transactions, you can use the `customAttributes` object which will be passed back alongside enriched transaction data.

### Sending your batch

Once you have prepared your batch(es) you can send up to 10 requests per second to our batch enrichment endpoints.

<Note>
  Uploading large batches can take a while, so we recommend increasing the timeout of your client. We support up to a 120 second timeout window.
</Note>

Below we've provided an example request for reference.

<CodeGroup>
  ```bash shell theme={null}
  curl --request POST \
  --url https://east.sandbox.spade.com/batches/transactions/cards/enrich \
  --header 'content-type: application/json' \
  --header 'X-Api-Key: SPADE-API-KEY' \
  --data '{
    "callbackUrl": "https://example.com/callback",
    "transactions": [
      {
        "transactionId": "53DCD6BD-6220-408C-8B49-7761C9D35FC3",
        "userId": "2AF17111-7270-498F-8731-C023B1A85A48",
        "merchantName": "Wal-Mart Super Center",
        "amount": "5.00",
        "location": {
          "city": "PORT ORANGE",
          "region": "12",
          "country": "US",
          "postalCode": "11111"
        },
        "acquirerId": "",
        "occurredAt": "2021-01-01",
        "categoryCode": "5488",
        "categoryType": "MCC",
        "currencyCode": "USD"
      },
      {
        "transactionId": "747DF4B4-4D30-4E44-B1C7-0C3CA99AD55D",
        "userId": "7BDCC2E9-0E87-435E-866D-54C2552282C0",
        "merchantName": "Amazon",
        "acquirerId": "000000000123456",
        "amount": "25.23",
        "currencyCode": "USD",
        "occurredAt": "2022-06-15 18:27:51Z",
        "categoryCode": "5812",
        "categoryType": "MCC",
        "location": {
          "address": "1234 W 5th Ave Suite 100",
          "city": "New York",
          "region": "NY",
          "country": "USA",
          "postalCode": "10001",
          "latitude": 45,
          "longitude": 120
        }
      }
    ]
  }'
  ```

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

  # Prepare your batch of transactions
  transactions = [
      {
          "userId": "user_123",
          "merchantName": "Walmart",
          "amount": 50.23,
          "currencyCode": "USD",
          "occurredAt": "2024-03-21T15:30:00Z",
          "categoryType": "MCC",
          "categoryCode": "5411",
          "transactionId": "tx_123",  # Must be unique within the batch
          "location": {
              "city": "Seattle",
              "region": "WA",
              "country": "USA"
          }
      },
      # ... more transactions ...
  ]

  # Submit the batch (without a callbackUrl)
  response = requests.post(
      "https://east.sandbox.spade.com/batches/transactions/cards/enrich",
      json={"transactions": transactions},
      headers={"X-Api-Key": "<Your API Key Here>"}
  )

  # Or submit with a callbackUrl
  response = requests.post(
      "https://east.sandbox.spade.com/batches/transactions/cards/enrich",
      json={
          "transactions": transactions,
          "callbackUrl": "https://your-callback-endpoint.com/batch-notifications"
      },
      headers={"X-Api-Key": "<Your API Key Here>"}
  )

  batch_id = response.json()["batchId"]
  print(f"Batch submitted with ID: {batch_id}")
  ```
</CodeGroup>

Once successfully submitted we will send back a response object that includes the `batchId` which you'll use later to retrieve the enriched transactions.

```json Batch response theme={null}
{
  "batchId": "8590f0f5-2a4b-431e-baa8-cd3bfff22020",
  "status": "pending",
  "batchSize": 2,
  "submittedAt": "2025-06-30T21:46:10.827344Z"
}
```

<Warning>
  You must use the `batchId` to retrieve enriched data, so make sure you are storing it!
</Warning>

### Handling failed batches

If your submitted batch fails we will return a `400` response with error details. Below is an example of an error response with duplicate `transactionIds`:

```json theme={null}
{
  "transactions": [
    "Transaction at index 1 has a duplicate ID."
  ]
}
```

Using the error details, make the changes required and re-submit the batch.

## Checking batch status

A batch job can be in one of four states:

\* `pending`: The batch has been accepted but processing hasn't started

\* `running`: The batch is currently being processed

\* `completed`: All transactions have been enriched and results are ready

\* `failed`: The batch encountered an error and couldn't be processed

We provide options for updates on the batch status either via webhooks or polling the status endpoint. We strongly recommend using webhooks as this reduces integration complexity as well as unnecessary API calls.

**Using webhooks**

If you provided a `callbackUrl` in your batch submission, we'll send a POST request to that URL when processing completes with the `batchId` and the `status`.

We will also include a token in the `X-Webhook-Token` header that you should compare against the callback token provided to you by your Spade representative. Contact your Spade representative if you do not have this token.

Here is an example Flask endpoint you could use to receive callbacks.

<CodeGroup>
  ```python Callback example expandable theme={null}
  from flask import Flask, request, jsonify

  app = Flask(__name__)

  @app.route('/batch-notifications', methods=['POST'])
  def batch_notification():
      webhook_token = request.headers.get('X-Webhook-Token')
      expected_token = "your-callback-token-from-spade"  # Store this securely

      if not webhook_token or webhook_token != expected_token:
          raise InvalidTokenError()

      data = request.json
      batch_id = data["batchId"]
      status = data["status"]

      if status == "completed":
          fetch_batch_results(batch_id)
      elif status == "failed":
          log_batch_failure(batch_id)
  ```
</CodeGroup>

**Polling for status**

You can poll the status endpoint until the status changes to a `completed` status. We recommend implementing an exponential backoff strategy when polling for status.

<CodeGroup>
  ```python Polling example expandable theme={null}
  def wait_for_batch_completion_with_backoff(batch_id, max_attempts=10, initial_delay=60):
      """Wait for batch completion with exponential backoff"""
      delay = initial_delay

      while True:
          response = requests.get(
              f"https://east.sandbox.spade.com/batches/{batch_id}",
              headers={"X-Api-Key": "<Your API Key Here>"}
          )
          status = response.json()["status"]

          if status == "completed":
              return True
          elif status == "failed":
              raise Exception("Batch processing failed")

          print(f"Sleeping for {delay} seconds before checking status again")
          time.sleep(delay)
          delay = max(delay * 2, 60)

      raise Exception("Timeout waiting for batch completion")
  ```
</CodeGroup>

<Note>
  If the batch results in a `failed` status you will need to re-send the batch for processing.
</Note>

### Retrieving enriched data

Once the batch status is `completed` you can retrieve the enriched data by calling the /results endpoint.

```json theme={null}
{
  "batchId": "8590f0f5-2a4b-431e-baa8-cd3bfff22020",
  "status": "completed"
}
```

<Warning>
  The `/batches/{batchId}/results` endpoint will only return results when the batch status is `completed`. If you request results before completion, you'll receive a 202 status code indicating that the results aren't yet available.
</Warning>

The results endpoint will return the `batchId` in addition to a `results` array that includes all of the transactions sent in the batch.

Each transaction object in the `results` array will contain:

* Your original`transactionId `
* Any `customAttributes` provided in the transaction
* A`status` code (either `200` or `400`)
* If successful, the enriched transaction data will include an `enrichmentId`
* If unsuccessful an error object

Below is an example response returned:

```json expandable theme={null}
{
  "batchId": "8590f0f5-2a4b-431e-baa8-cd3bfff22020",
  "results": [
    {
      "statusCode": 200,
      "transactionInfo": {
        "type": "spending",
        "subType": null,
        "display": {
          "name": "Walmart",
          "categoryName": "Department Stores",
          "graphic": "https://static.v2.spadeapi.com/logos/d730906bf1a849f19939f27390170a6d/light.png",
          "graphicSource": "counterparty"
        },
        "thirdParties": [],
        "spendingInfo": {
          "channel": {
            "value": "physical"
          }
        },
        "transferInfo": null,
        "atmInfo": null,
        "isAccountVerification": null,
        "isPeerToPeer": null,
        "isDigitalWallet": null,
        "transactionId": "53DCD6BD-6220-408C-8B49-7761C9D35FC3",
        "recurrenceInfo": null,
        "riskInsights": {
          "irregularWebPresenceDetected": false,
          "negativeOnlineSentiment": false,
          "highRiskEntity": false,
          "riskyIndustry": false,
          "cardAcceptanceHistory": "extensive"
        }
      },
      "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"
            }
          ],
          "location": [
            {
              "id": "380e18b7-bf9e-3545-b27e-80e36301c540",
              "address": "1590 Dunlawton Ave",
              "addressLine1": "1590 Dunlawton Ave",
              "addressLine2": null,
              "city": "Port Orange",
              "region": "FL",
              "postalCode": "32127",
              "country": "USA",
              "phoneNumber": "+13867610191",
              "latitude": 29.116753,
              "longitude": -81.019165,
              "matchScore": 85.35
            }
          ],
          "matchScore": 89.62,
          "logo": "https://static.v2.spadeapi.com/logos/d730906bf1a849f19939f27390170a6d/light.png",
          "medianSpendPerTransaction": null,
          "phoneNumber": "+14792734000",
          "website": "https://www.walmart.com/"
        }
      ],
      "enrichmentId": "967c0acc-92a0-4212-bbed-3a7209b58bf6"
    },
    {
      "statusCode": 200,
      "transactionInfo": {
        "type": "spending",
        "subType": null,
        "display": {
          "name": "Amazon",
          "categoryName": "Online Marketplace",
          "graphic": "https://static.v2.spadeapi.com/logos/5f35110e8de74f9cadc6b28bf87dd5b6/light.png",
          "graphicSource": "counterparty"
        },
        "thirdParties": [],
        "spendingInfo": {
          "channel": {
            "value": "digital"
          }
        },
        "transferInfo": null,
        "atmInfo": null,
        "isAccountVerification": null,
        "isPeerToPeer": null,
        "isDigitalWallet": null,
        "transactionId": "747DF4B4-4D30-4E44-B1C7-0C3CA99AD55D",
        "recurrenceInfo": null,
        "riskInsights": {
          "irregularWebPresenceDetected": false,
          "negativeOnlineSentiment": false,
          "highRiskEntity": false,
          "riskyIndustry": false,
          "cardAcceptanceHistory": "limited"
        }
      },
      "counterparty": [
        {
          "id": "5f35110e-8de7-4f9c-adc6-b28bf87dd5b6",
          "name": "Amazon",
          "legalName": "Amazon.com, Inc.",
          "industry": [
            {
              "id": "011-000-000-000",
              "name": "Retail",
              "icon": "https://static.v2.spadeapi.com/categories/ee4ee39fd5474d31ac42f9e606b9040a/light.png"
            },
            {
              "id": "011-010-000-000",
              "name": "Online Marketplace",
              "icon": "https://static.v2.spadeapi.com/categories/b4b0d249b40249acb7445027d4574fc5/light.png"
            }
          ],
          "location": [
            {
              "id": null,
              "address": "1234 W 5th Ave Suite 100",
              "addressLine1": "1234 W 5th Ave Suite 100",
              "addressLine2": null,
              "city": "New York",
              "region": "NY",
              "postalCode": "10001",
              "country": "USA",
              "phoneNumber": null,
              "latitude": 45,
              "longitude": 120,
              "matchScore": null
            }
          ],
          "matchScore": 98.26,
          "logo": "https://static.v2.spadeapi.com/logos/5f35110e8de74f9cadc6b28bf87dd5b6/light.png",
          "medianSpendPerTransaction": null,
          "phoneNumber": "+12062661000",
          "website": "https://www.amazon.com"
        }
      ],
      "enrichmentId": "aae9c6bc-1cea-4ca8-8f70-dc85e2d21224"
    }
  ]
}
```

<CodeGroup>
  ```python Retrieving results theme={null}
  response = requests.get(
      f"https://east.sandbox.spade.com/batches/{batch_id}/results",
      headers={"X-Api-Key": "<Your API Key Here>"}
  )

  results = response.json()["results"]

  for enriched_transaction in results:
      print(f"Enriched transaction: {enriched_transaction['enrichmentId']}")
      # ... process the enrichment ...
  ```
</CodeGroup>

### **Handling errors**:

For transactions that resulted in errors we will provide an `errors` object that includes detailed information that caused the error. An example is included below for a request that did not include a `merchantName`

```json theme={null}
    {
      "statusCode": 400,
      "transactionInfo": {
        "transactionId": "53DCD6BD-6220-408C-8B49-7761C9D35FC3"
      },
      "errors": {
        "merchantName": [
          "This field is required."
        ]
      }
    }
```

See our [enrichment guide](https://docs.spade.com/reference/getting-started#implementation-notes) for more details on handling errors from our API.
