This guide describes how to use the Loyalty Customer Match Service in the
Merchant API. This service enables merchants and third-party loyalty providers
acting on behalf of merchants to manage customer loyalty data, such as user
identifiers and tier information, for organic personalization on Google Search,
without requiring an active Google Ads account.

## Overview

Use the Loyalty Customer Match Service to upload loyalty data, which is then
used to provide organic loyalty personalization features on Google Search, such
as displaying member-specific pricing. You use the `ManageLoyaltyCustomerMatch`
custom method to associate your customers with loyalty program tiers, enabling
you to **insert** , **update** , or **remove** their loyalty status based on user
identifiers.

## Key concepts

- **Unified interface:** A unique endpoint to add, update, or remove customer loyalty tier details.
- **Privacy-first design:** To protect user privacy and prevent unauthorized account probing, the API does not support **GET** or **LIST** operations, ensuring that data is managed without retrieval or auditing.
- **Flexible identification:** Match users using at least one valid identifier, such as an email address, physical address, or phone number.
- **Consent-based processing:** The service stores and uses customer data only when the end-user has granted the necessary [consent to Google](https://support.google.com/googleshopping/answer/13005558). To protect and prevent account existence probing or consent status, the service returns a silent success if a match is not made or consent is not granted.

## Prerequisites

Follow these requirements to use the Loyalty Customer Match Service:

- **Account setup:** Ensure you have an active Merchant Center account (or authorized access to the merchant's account if you are a third-party loyalty provider). You don't need to create a Google Ads account to use the Loyalty Customer Match Service.
- **Loyalty program configuration:** Enable the [loyalty program](https://support.google.com/merchants/answer/12827255&ref_topic=15164622) in your Merchant Center account, and make sure you have defined loyalty tiers.
- **Tier order awareness:** Be aware of the order in which your loyalty tiers are defined in the Merchant Center UI. The API uses this exact sequence for its enum mapping.

## Method: `ManageLoyaltyCustomerMatch`

The `ManageLoyaltyCustomerMatch` method serves as the central interface for
managing customer loyalty associations. Based on the input provided, the service
automatically determines whether to insert, update, or remove the loyalty tier
status of a customer. The operation is
[idempotent](https://en.wikipedia.org/wiki/Idempotence): repeated identical
requests have the same effect as a single request.

The following request demonstrates how to manage customer loyalty associations
through the API:

    POST https://merchantapi.googleapis.com/{API_VERSION}/accounts/{ACCOUNT_ID}/loyaltyCustomers:manage

This request defines the following required path parameters:

- <var translate="no">`API_VERSION`</var>: The API version such as v1.
- <var translate="no">`ACCOUNT_ID`</var>: The Merchant Center account ID.

Include a `loyaltyCustomer` object in the request body.

    {
        "userIdentifier": {
          "emailAddress": "string",
          "address": {
            "addressLines": ["string"],
            "locality": "string",
            "administrativeArea": "string",
            "postalCode": "string",
            "regionCode": "string"
          },
          "phoneNumber": "string"
        },
        "loyaltyTier": "LoyaltyTier",
        "pointBalance": "integer"
      }

**loyaltyCustomer fields**

- **userIdentifier**: The set of identifiers used to match the customer. At least one field within userIdentifier must be provided and valid.
- **loyaltyTier** : The loyalty tier to associate with the customer. Maps to the order of the tier in the Merchant Center setup. For details, see [Understanding `loyaltyTier` mapping](https://developers.google.com/merchant/api/guides/loyalty/customer-match-service#understanding-loyaltytier-mapping). Use **NON_MEMBER** to remove an existing association.
- **pointBalance**: The customer's current points balance.

**userIdentifier fields**

Provide at least one of the following fields:

- **emailAddress**: The customer's email address.
- **address**: The customer's physical address. PostalCode is required.
- **phoneNumber**: The customer's phone number. E.164 format is recommended.

## Understand `loyaltyTier` mapping

The API does not use the custom names. The `loyaltyTier` enum values (`TIER1`
to `TIER7`) are semantic labels. They don't use the custom names (for example,
"Gold Rewards") or custom labels (for example, "gold_tier") you assigned in your
Merchant Center UI. Instead, they map strictly to the order in which
you defined your tiers in the loyalty program settings in the
Merchant Center:

- `TIER1`: Corresponds to the first tier listed in your Merchant Center loyalty program configuration.
- `TIER2`: Corresponds to the second tier listed in your Merchant Center loyalty program configuration.
- `TIER3` through `TIER7`: Correspond to the third through seventh tiers listed in your Merchant Center loyalty program configuration.

**Example:**

If your Merchant Center loyalty program has tiers defined in this order:

1. Tier Name: **"Silver Status"** , Tier Label: **"silver"**
2. Tier Name: **"Gold Member"** , Tier Label: **"gold"**
3. Tier Name: **"Platinum Elite"** , Tier Label: **"platinum"**

Then, in [`accounts.loyaltyCustomers.manage`](https://developers.google.com/merchant/api/reference/rest/loyaltycustomers_v1/accounts.loyaltyCustomers/manage)
API calls:

- To assign a customer to **"Silver Status"** , you must use `loyaltyTier: TIER1`.
- To assign a customer to **"Gold Member"** , you must use `loyaltyTier: TIER2`.
- To assign a customer to **"Platinum Elite"** , you must use `loyaltyTier: TIER3`.

> [!IMPORTANT]
> **Important:** To use the correct API enum value, check the order of tiers in your Merchant Center loyalty program settings.

**LoyaltyTier enum values**

- `TIER1`
- `TIER2`
- `TIER3`
- `TIER4`
- `TIER5`
- `TIER6`
- `TIER7`
- `NON_MEMBER` (Used to signal removal of the customer's loyalty association)

## Understand the ManageLoyaltyCustomerMatch response body

The `ManageLoyaltyCustomerMatch` method returns a
`ManageLoyaltyCustomerMatchResponse` object:

    {
      "loyaltyCustomer": {
        // loyaltyCustomer object from the request
      }
    }

### Important considerations for responses

- **Successful upsert (data stored):** To successfully store or update a
  customer's loyalty tier association, meet the following conditions:

  - **you match a Google user** with the provided `userIdentifier`
  - **you set** the `loyaltyTier` in the request to a valid value other than `NON_MEMBER`
  - **the matched user has consented** to loyalty data usage

The response contains the `loyaltyCustomer` object from your request, indicating
that the service successfully processed and stored the data:

    {
      "loyaltyCustomer": {
        "userIdentifier": {
         "emailAddress": "customer@example.com"
        },
        "loyaltyTier": "TIER2",
        "pointBalance": 1500
        }
    }

- **Successful deletion:** To successfully remove any existing loyalty association for the customer with this merchant, the following conditions must be fulfilled:
  - **you match a Google user** with the provided `userIdentifier`
  - **you set** the `loyaltyTier` in the request to `NON_MEMBER`

The response is an empty JSON object:

    {}

- **No match / no consent (silent success):** If the provided `userIdentifier` does not match a Google Account, or if the matched user has not consented to loyalty data usage, the API returns an **HTTP 200 OK** status with an empty JSON object: `{}`. This happens for both upsert and removal attempts.

> [!IMPORTANT]
> **Important:** This is not an error. It is a deliberate privacy measure to prevent inferring user account existence or consent status. In this case, the system doesn't write or delete any data.

## Examples

**TIER1** corresponds to the first defined tier (for example, **'Basic'** ), and
**TIER2** corresponds to the second tier (for example, **'Premium'**).

To add a customer to **TIER2** or update their status using an email address,
send the following request:

    POST
    "https://merchantapi.googleapis.com/v1/accounts/{ACCOUNT_ID}/loyaltyCustomers:manage"
      -d '{
          "userIdentifier": {
            "emailAddress": "customer@example.com"
          },
          "loyaltyTier": "TIER2",
          "pointBalance": 1500
      }'

When a user is successfully matched and has consented, the API returns the
following response:

    {
      "loyaltyCustomer": {
        "userIdentifier": {
          "emailAddress": "customer@example.com"
        },
        "loyaltyTier": "TIER2",
        "pointBalance": 1500
      }
    }

When there is no match or the user hasn't consented, the API returns the
following response:

    {}

To remove a customer's loyalty association using a phone number, send the
following request:

    POST
    "https://merchantapi.googleapis.com/v1/accounts/{ACCOUNT_ID}/loyaltyCustomers:manage" \
      -d '{
          "userIdentifier": {
            "phoneNumber": "+18005550132"
          },
          "loyaltyTier": "NON_MEMBER"
      }'

Regardless of whether a record existed, the API returns the following success
response:

    {}

To add or update a customer using multiple identifiers, send the following
request:

    POST
    "https://merchantapi.googleapis.com/v1/accounts/{ACCOUNT_ID}/loyaltyCustomers:manage" \
      -d '{
          "userIdentifier": {
            "emailAddress": "user@example.com",
            "address": {
              "postalCode": "94043",
              "regionCode": "US"
            }
          },
          "loyaltyTier": "TIER1"
      }'

*Response is similar to the first example, depending on match and consent.*

## Error handling

The API uses standard HTTP codes. Common error strings include:

|---|---|---|
| **HTTP code** | **Error String** | **Description** |
| 400 | INVALID_ARGUMENT | Missing **user_identifier** or **loyalty_tier**, or identifier is empty. |
| 401 | UNAUTHENTICATED | Invalid or missing credentials. |
| 403 | PERMISSION_DENIED | The authenticated user does not have access to the specified Merchant Center account. |
| 404 | NOT_FOUND | The specified loyalty tier label does not exist in your configuration. |
| 412 | FAILED_PRECONDITION | You have not configured a loyalty program in your account. |
| 429 | RESOURCE_EXHAUSTED | Quota limit reached. |

> [!NOTE]
> **Note:** If a user cannot be matched (for example, due to lack of consent or user not found), the API returns a **200 OK** status with a default **loyaltyCustomer** resource. This is a privacy measure to prevent probing for account existence.

### Error examples

Example for **404 NOT_FOUND:**

Any valid request to an account ID that does not have a loyalty program
configured.

The API returns the following error response:

    {
      "error": {
        "code": 404,
        "message": "The loyalty program is not found for account: {ACCOUNT_ID}.",
        "status": "NOT_FOUND",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "notFound",
            "domain": "merchantapi.googleapis.com",
            "metadata": {
              "ACCOUNT_ID": "{ACCOUNT_ID}",
              "REASON": "NOT_FOUND_LOYALTY_PROGRAM"
            }
          }
        ]
      }
    }

**Reason:** The merchant account in the path does not have an active loyalty
program.

Examples of **400 INVALID_ARGUMENT:**

An error occurs if the request contains an invalid value for the `loyaltyTier`
field:

    {
      "loyaltyCustomer": {
        "userIdentifier": {
          "emailAddress": "customer@example.com"
        },
        "loyaltyTier": "TIER11",
        "pointBalance": 100
      }
    }

The API returns the following error response:

    {
      "error": {
        "code": 400,
        "message": "Invalid value at 'loyalty_customer.loyalty_tier' (type.googleapis.com/google.shopping.merchant.loyaltycustomers.v1.LoyaltyCustomer.LoyaltyTier), \"TIER11\"",
        "status": "INVALID_ARGUMENT",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.BadRequest",
            "fieldViolations": [
              {
                "field": "loyalty_customer.loyalty_tier",
                "description": "Invalid value at 'loyalty_customer.loyalty_tier' (type.googleapis.com/google.shopping.merchant.loyaltycustomers.v1.LoyaltyCustomer.LoyaltyTier), \"TIER11\""
              }
            ]
          }
        ]
      }
    }

**Reason:** **TIER11** is not a valid enum value for `loyaltyTier`. The same
error can happen when you try to specify **TIER2** when there's only one tier
available.

An error occurs if the required `loyaltyTier` field is missing from the request
body:

    {
      "loyaltyCustomer": {
        "userIdentifier": {
          "emailAddress": "customer@example.com"
        },
        "pointBalance": 100
      }
    }

The API returns the following error response:

    {
      "error": {
        "code": 400,
        "message": "[loyalty_customer.loyalty_tier] Required field not provided: loyalty_customer.loyalty_tier",
        "status": "INVALID_ARGUMENT",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "required",
            "domain": "merchantapi.googleapis.com",
            "metadata": {
              "FIELD_LOCATION": "loyalty_customer.loyalty_tier",
              "REASON": "MISSING_REQUIRED_FIELD"
            }
          }
        ]
      }
    }

**Reason:** The `loyaltyTier` field is required.

An error occurs if an address identifier is incomplete, such as when the
postalCode field is missing:

    {
      "loyaltyCustomer": {
        "userIdentifier": {
          "address": {
            "locality": "Sunnyvale",
            "administrativeArea": "CA",
            "regionCode": "US"
          }
        },
        "loyaltyTier": "TIER1",
        "pointBalance": 100
      }
    }

The API returns the following error response:

    {
      "error": {
        "code": 400,
        "message": "[loyalty_customer.user_identifier] The format of loyalty_customer.user_identifier does not match the expected format ... Value: at least one valid user identifier should be provided.",
        "status": "INVALID_ARGUMENT",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "invalid",
            "domain": "merchantapi.googleapis.com",
            "metadata": {
              "FIELD_NAME": "loyalty_customer.user_identifier",
              "REASON": "INVALID_VALUE"
            }
          }
        ]
      }
    }

**Reason:** An address is provided, but it's missing the required `postalCode`
field, so it's not considered a valid identifier.

An error occurs if you request a tier index that is out of bounds for the
configured program:

**Scenario:** The merchant has only one tier configured in the
Merchant Center.

    {
      "loyaltyCustomer": {
        "userIdentifier": {
          "emailAddress": "customer@example.com"
        },
        "loyaltyTier": "TIER2",
        "pointBalance": 100
      }
    }

The API returns the following error response:

    {
      "error": {
        "code": 400,
        "message": "[loyalty_customer.loyalty_tier] The format of loyalty_customer.loyalty_tier does not match the expected format `valid LoyaltyTier`. Value: TIER2.",
        "status": "INVALID_ARGUMENT",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "invalid",
            "domain": "merchantapi.googleapis.com",
            "metadata": {
              "FIELD_NAME": "loyalty_customer.loyalty_tier",
              "PATTERN": "valid LoyaltyTier",
              "FIELD_VALUE": "TIER2",
              "REASON": "INVALID_VALUE"
            }
          }
        ]
      }
    }

**Reason:** **TIER2** is requested, but the loyalty program linked to the
account doesn't have a second tier defined.

An error occurs if the request contains a malformed `emailAddress`:

    {
      "loyaltyCustomer": {
        "userIdentifier": {
          "emailAddress": "customer@google"
        },
        "loyaltyTier": "TIER1",
        "pointBalance": 100
      }
    }

The API returns the following error response:

    {
      "error": {
        "code": 400,
        "message": "[loyalty_customer.user_identifier] The format of loyalty_customer.user_identifier does not match the expected format `email_address: \t \"customer@google\"\n`. Value: at least one valid user identifier should be provided.",
        "status": "INVALID_ARGUMENT",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "invalid",
            "domain": "merchantapi.googleapis.com",
            "metadata": {
              "FIELD_NAME": "loyalty_customer.user_identifier",
              "REASON": "INVALID_VALUE"
            }
          }
        ]
      }
    }

**Reason:** The email address format is invalid.

An error occurs if the `userIdentifier` object is empty:

    {
      "loyaltyCustomer": {
        "userIdentifier": {},
        "loyaltyTier": "TIER1",
        "pointBalance": 100
      }
    }

The API returns the following error response:

    {
      "error": {
        "code": 400,
        "message": "[loyalty_customer.user_identifier] Required field not provided: loyalty_customer.user_identifier",
        "status": "INVALID_ARGUMENT",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "required",
            "domain": "merchantapi.googleapis.com",
            "metadata": {
              "FIELD_LOCATION": "loyalty_customer.user_identifier",
              "REASON": "MISSING_REQUIRED_FIELD"
            }
          }
        ]
      }
    }

**Reason:** The `userIdentifier` object is present but contains no actual
identifier fields.

**Note: Identifier validation.:**

- The API performs basic format checks on identifiers (for example, email structure, presence of `postalCode` in addresses).
- However, some identifiers that pass initial checks might not match any Google user account or may not be in a format recognized by the backend matching system. In such cases, you will receive the silent success empty response `{}` with HTTP status `200 OK`.

## Best practices

Follow these best practices to optimize your integration.

- **For large-scale integration:** Because the API operates on a per-request
  basis, client-side parallelism is required to achieve the necessary
  throughput for large datasets. You should design your integration to manage
  multiple concurrent requests. For guidance on how to structure your
  implementation to handle higher volumes through parallelization, refer to
  our guide on [how to send multiple requests](https://developers.google.com/merchant/api/guides/send-multiple-requests).

- **Quota management:** The default quota is **1,000,000 requests/day**
  and **10,000 requests/minute** . To see how you can monitor and check your
  quotas, see [Quotas and limits](https://developers.google.com/merchant/api/guides/quotas-limits).

- **Prioritize email address:** Whenever possible, include the customer's
  `emailAddress` in the `userIdentifier`. Email addresses are generally the
  most accurate and reliable identifier for matching users to their Google
  Accounts.

- **Handle empty responses:** Design your application to correctly interpret
  empty `{}` responses as a success, understanding that it means the data
  was not stored due to privacy reasons (no match or no consent). **Don't
  retry the request.**

- **Verify tier order:** Always confirm the order of your loyalty tiers in the
  Merchant Center UI to ensure you are using the correct
  `TIER1` through `TIER7` enum values in your API calls. This mapping is based
  on the defined order in the UI, not their names.

- **Monitor errors:** Log and monitor API responses, paying attention to any
  `4xx` errors to catch integration issues, especially `404` errors which
  might indicate a mismatch in tier understanding.