NAV
shell

Introduction

Welcome to the Merit Points API.

This API enables partners to seamlessly integrate with Merit's Points platform, providing a comprehensive set of services for both Points Exchange and Points Earn. Through these APIs, partners can award points to customers and enable the exchange across supported loyalty programs.

The API is designed to support secure, scalable, and real-time integrations, allowing partners to deliver a seamless loyalty experience while leveraging Merit's extensive ecosystem.

Base URL

Make sure to replace {{BaseURL}} with the relevant staging or production URL.

Base URL for Merit API endpoints

Description URL
Staging https://api.sandbox.meritincentives.com/points-exchange
Prod
Content-Type application/json
Authorization Bearer <TOKEN>

Authentication

Sample API call with the Authorization header

curl "{{BaseURL}}/path/to/resource" \
  -H "Authorization: Bearer <TOKEN>"

Make sure to replace with your Token.

All the API requests to the server should include Token in the Authorization header:

Authorization: Bearer <TOKEN>

You must replace TOKEN with your Token.

Token will be provided while onboarding.

Authorization (Client Credentials Flow)

This API uses OAuth 2.0 Client Credentials Grant for authentication.

Overview

Your server first obtains a Bearer access token from the Token API using its assigned client_id and client_secret. Include the returned token in the Authorization header of every API request:

Authorization: Bearer <TOKEN>

Each access token is valid for the number of seconds returned in expires_in, currently 86400 seconds (24 hours). Request a new token before the current token expires.

OAuth Base URL

Description URL
Staging https://api.sandbox.meritincentives.com
Production https://api.meritincentives.com
Content-Type application/x-www-form-urlencoded

Token Endpoint

POST {{IdentityBaseURL}}/identity/v1/oauth2/token

Used to obtain the access token.

Headers

Header Relevance Value
Content-Type Required application/x-www-form-urlencoded

Body Parameters

Parameter Relevance Value
grant_type Required Must be set to client_credentials.
client_id Required The client ID issued to your application, for example 4f9xxxx8a1e3f6xxxxxx.
client_secret Required The client secret issued to your application, for example Sxxx8v9Gxxxx.

CURL REQUEST

curl -X POST "{{IdentityBaseURL}}/identity/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=<CLIENT_ID>" \
  --data-urlencode "client_secret=<CLIENT_SECRET>"

Response

On success, the endpoint returns HTTP 200 with:

Key Type Description
access_token string Access token used to authenticate API requests.
token_type string Token type; always bearer.
expires_in integer Token validity period in seconds.

RESPONSE - 200 OK

{
  "access_token": "NgCXRKc...MzYjw",
  "token_type": "bearer",
  "expires_in": 86400
}

Client credentials are environment-specific. Store client_secret only in a secure server-side secret store. Never include it in browser applications, mobile applications, POS logs, URLs, or API requests other than the Token Endpoint.

Common API Reference

List my loyalty programs

GET {host}/api/v1/loyalty-programs?limit=10&offset=0

This API lists all the loyalty programs that (usually) belong to the API Client or that have given direct earn/burn access to the API client. Usually, the length of the response list is not more than 1.

Headers

Key Value
Accept-Language en
Authorization Bearer <TOKEN>

Possible error codes

Response:

Loyalty programs JSON structured like this:

[
  {
    "loyalty_program_id": "TOPSHERPA",
    "loyalty_program_name": "TopSherpa",
    "currency_id": "TOPSHERPA",
    "currency_name": "Top Sherpa Coins",
    "logo_url": "logo.com",
    "banner_url": "banner.com",
    "required_fields": [
      {
        "id": "id",
        "name": "Member ID"
      }
    ],
    "validation_assurance": "CONFIRMED",
    "description": "",
    "terms_and_conditions": "",
    "category": "Airline",
    "brand": "TopSherpa",
    "is_default": true
  }
]

Loyalty Program

Field Description Type
loyalty_program_id Loyalty program slug string
loyalty_program_name Loyalty program name string
currency_id Currency slug used for exchange string
currency_name Currency name string
logo_url Loyalty program logo string
banner_url Loyalty program banner string
category Loyalty program category string
brand Loyalty program brand string
description Loyalty program description string
terms_and_conditions Loyalty program T&C string
required_fields Required fields to create exchange. Supported Member Fields object
validation_assurance Validation reliability: CONFIRMED, PROBABLE, RULE_BASED, or NONE string

Member Validation

Member validation is shared by the Points Exchange and EARN APIs. It supports loyalty programs available through either POINTS_TO_POINTS or FIAT_TO_POINTS partnerships.

POST {host}/api/v1/validate/member

Headers

Key Value
Authorization Bearer <TOKEN>

Request body

Validate member Request JSON structured like this:

{
  "currency_id": "MERIT-COIN",
  "member": {
    "id": "member1",
    "first_name": "Example first name",
    "last_name": "Example last name",
    "mobile_number": "123456789",
    "country_calling_code": "123",
    "email": "test@email.com"
  }
}
Field Description Type Required
currency_id Currency ID. string True
member The required fields depend on the partner and are returned by List Exchange Out Loyalty Programs or List EARN Partners. For example, if a program requires only id and last_name, those fields are mandatory and the others are optional. Supported Member Fields object True

Possible error codes

Response 200

Validate member JSON structured like this:

{
  "valid": true,
  "status": "VALID"
}
Field Description Type
valid Backward-compatible boolean result derived from status boolean
status Validation outcome: VALID, INVALID, UNABLE_TO_REACH, or NOT_SUPPORTED string

Validation Outcomes

Status valid Meaning
VALID true Validation completed and the partner or configured rules accepted the member
INVALID false Validation completed and the member was rejected, not found, ineligible, or blocked
UNABLE_TO_REACH false Online validation could not reach the partner or receive a usable response
NOT_SUPPORTED true Pre-transaction validation is unavailable; the transaction may proceed and performs in-place validation

Supported Member Fields

Field Description Type
id The unique loyalty membership identifier string
first_name The first name used by the member to register with the loyalty program string
last_name The last name used by the member to register with the loyalty program string
mobile_number The mobile number used by the member to register with the loyalty program. This is without the country code prefix. Example: "5555555555". Country code should be passed separately under country_calling_code. string
country_calling_code The country code of the mobile number. Example: "966" (Without a '+') string
email The email used by the member to register with the loyalty program string

Validation Assurance

The loyalty program's validation_assurance communicates how much confidence clients should place in VALID or INVALID. Merit forwards decisions returned by partners and integration aggregators without increasing their assurance. A transaction may still fail even after VALID, because posting performs its own in-place validation.

validation_assurance VALID INVALID UNABLE_TO_REACH NOT_SUPPORTED
CONFIRMED Supported Supported Supported Not applicable
PROBABLE Supported Supported Supported Not applicable
RULE_BASED Supported Supported Not applicable Not applicable
NONE Not applicable Not applicable Not applicable Supported
Validation assurance Behavior
CONFIRMED The partner provides a direct validation API and treats VALID and INVALID as confident decisions. Temporary connectivity failures can return UNABLE_TO_REACH.
PROBABLE The partner or integration aggregator provides a validation API, but its VALID or INVALID decision may be an assumption. Merit forwards that decision unchanged. Connectivity failures can return UNABLE_TO_REACH.
RULE_BASED Merit applies offline rules supplied by the partner. Results are VALID or INVALID; no partner network call is made.
NONE Pre-transaction member validation is unavailable. The validation endpoint returns NOT_SUPPORTED, and validation occurs when the transaction is posted.

A VALID result does not guarantee that a later transaction will succeed. Member state may change, and transaction posting performs its own validation.

Transaction Status

Status Description
CREATED This is when the order is created in the system. The API Client will never see this status.
QUEUED This occurs when the client is a prepaid client and the client doesn't have enough funds in their account.
IN_PROCESS For prepaid clients, this is after successful payment. For postpaid clients, this is the first status of the transaction that the API Client sees.
COMPLETED Transaction completed successfully
FAILED Transaction failed

Status Code

Addition to Transaction Status. Status code gives more context about Status.

Status Code Description
SUCCESS Operation complete successfully
INSUFFICIENT_FUNDS Special status code for QUEUED. If client doesn't have enough funds in their account.
INVALID_MEMBER Member used to create transaction does not exists.
EXCHANGE_PARTNER_NOT_REACHABLE Something goes wrong. We cannot reach partner to create transaction
UNABLE_TO_PROCESS Transaction failed completely. Reach out to dev team for more details
MAXIMUM_AMOUNT_REACHED Amount of points used to create the transaction exceeds the limit.

Error Format

Errors structure

Field Description Type
code Error Code string
message Short description of the error string
details Optional Object. Details about error Optional object([string][string])

Validation Error Example:

{
  "code": "VALIDATION_ERROR",
  "message": "Validation error",
  "details": {
    "field": "body.destination_amount",
    "issue": "Input should be a valid integer, got a string"
  }
}

Client Error Example:

{
  "code": "HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM",
  "message": "You have no access to loyalty program",
  "details": {}
}
Error Code HTTP code Meaning
VALIDATION_ERROR 400 Bad Request -- Your request is invalid.
DUPLICATE_REFERENCE_ID 400 Bad Request -- Reference id should be unique.
INCORRECT_DESTINATION_AMOUNT 400 Bad Request -- Destination amount does not much destination amount after exchange.
INCORRECT_BONUS_AMOUNT 400 Bad Request -- Bonus amount does not much bonus amount for campaign.
CAMPAIGN_NOT_AVAILABLE 400 Bad Request -- Campaign expired or does not exists
MIN_AMOUNT_LIMIT_NOT_REACHED 400 Bad Request -- Destination amount has not reached the minimum amount limit.
MAX_AMOUNT_LIMIT_REACHED 400 Bad Request -- Destination amount has reached the maximum amount limit.
UNAUTHORIZED 401 Unauthorized -- Your API key is wrong.
ACCESS_DENIED 403 Forbidden -- You have no access to resource. General error.
HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM 403 Forbidden -- You have no access to loyalty program.
NOT_FOUND 404 Not Found -- The specified resource could not be found.
METHOD_NOT_ALLOWED 405 Method Not Allowed -- You tried to access a resource with an invalid method.
INTERNAL_ERROR 500 Internal Server Error -- We had a problem with our server. Try again later.

Points Exchange API

The Points Exchange API enables customers to exchange points across supported loyalty programs. For pre-transaction member checks and accepted member attributes, see Member Validation and Supported Member Fields.

List Exchange Out Loyalty Programs

GET {host}/api/v1/loyalty-programs/{loyalty_program_id}/exchange-out?limit=10&offset=0

List all the loyalty-programs to which your loyalty program can be exchanged to. The partnership details of the exchange are also returned as part of this response.

Headers

Key Value
Accept-Language en
Authorization Bearer <TOKEN>

Possible error codes

Loyalty Programs JSON structured like this:

[
  {
    "loyalty_program_id": "TOPSHERPA",
    "loyalty_program_name": "TopSherpa",
    "currency_id": "TOPSHERPA",
    "currency_name": "Top Sherpa Coins",
    "logo_url": "logo.com",
    "banner_url": "banner.com",
    "category": "Airline",
    "brand": "TopSherpa",
    "origin_to_destination_rate": 5.00000000,
    "destination_min_amount": 10,
    "destination_max_amount": 1000,
    "required_fields": [
      {
        "id": "id",
        "name": "Member ID"
      }
    ],
    "validation_assurance": "CONFIRMED",
    "description": "",
    "terms_and_conditions": "",
    "campaign": {
      "id": "56377294-572e-4155-b70a-c47867d6cd01",
      "name": "Campaign title",
      "start_at": 1727267961,
      "end_at": 1727267962,
      "reward_rule": {
        "type": "FIXED_PERCENTAGE",
        "rule": {
          "amount": 30
        }
      }
    }
  },
  {
    "loyalty_program_id": "EXAMPLE",
    "loyalty_program_name": "Example",
    "currency_id": "EXAMPLE",
    "currency_name": "Example Coin",
    "logo_url": "logo.com",
    "banner_url": "banner.com",
    "category": "Retail",
    "brand": "Example",
    "origin_to_destination_rate": 0.00816792,
    "destination_min_amount": 5,
    "destination_max_amount": 500,
    "required_fields": [
      {
        "id": "id",
        "name": "Member ID"
      },
      {
        "id": "first_name",
        "name": "Member name"
      }
    ],
    "validation_assurance": "RULE_BASED",
    "description": "",
    "terms_and_conditions": "",
    "campaign": null
  }
]

Path Parameters

Parameter Required Type Description
loyalty_program_id Yes String Loyalty program ID whose exchange-out partners are requested.

Query Parameters

Parameter Required Type Description
limit No Integer Maximum number of records; defaults to 10.
offset No Integer Number of records to skip; defaults to 0.

Partner Loyalty Program

Field Description Type
loyalty_program_id Deprecated. Use currency_id instead. This field will be removed in a future API version. string
loyalty_program_name Loyalty program name string
currency_id Currency slug used for exchange string
currency_name Currency name string
logo_url Loyalty program logo string
banner_url Loyalty program banner string
category Loyalty program category string
brand Loyalty program brand string
description Loyalty program description string
terms_and_conditions Loyalty program T&C string
origin_to_destination_rate Conversion rate. How many origin currency you need for 1 destination currency float
destination_max_amount int
destination_min_amount int
required_fields Required fields to create exchange. Supported Member Fields object
validation_assurance Validation reliability: CONFIRMED, PROBABLE, RULE_BASED, or NONE string
campaign Campaign object

Campaign

Field Description Type
id Campaign id string
start_at Epoch time when campaign start int
end_at Epoch time when campaign end int
reward_rule Reward Rule object

Reward Rule

Field Description Type
type Reward type. Currently supported only FIXED_PERCENTAGE string
rule Reward rule. Object stores all the necessary information to properly calculate reward object

Calculate Exchange

POST {host}/api/v1/calculate-exchange/

Headers

Key Value
Authorization Bearer <TOKEN>

Request body:

Create Exchange JSON structured like this:

{
  "origin_amount": 50,
  "origin_currency_id": "MERIT-COIN",
  "destination_currency_id": "TOPSHERPA",
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
Field Description Type Required
origin_amount int True
origin_currency_id Origin Currency ID string True
destination_currency_id Destination Currency ID string True
campaign_id Campaign applied to transaction string False

Possible error codes

Response

Create Exchange JSON structured like this:

{
  "origin_amount": 50.0,
  "destination_amount": 100.0,
  "bonus_amount": 30,
  "total_amount": 130,
  "origin_currency_id": "MERIT-COIN",
  "destination_currency_id": "AL-FURSAN",
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
Field Description Type
origin_amount int
destination_amount int
origin_currency_id Origin Currency ID string
destination_currency_id Destination Currency ID string
bonus_amount Bonus amount int
campaign_id Campaign applied to transaction str
total_amount Total amount of received points int

Create Exchange

API to add points to the destination loyalty program.

This API operates asynchronously; the API accepts the request and responds immediately after performing a few validations.

The actual addition of points to the destination loyalty program accounts occur in the background.

Clients will be notified of the final status either through Exchange Webhooks or they can query using Get Exchange By reference_id.

POST {host}/api/v1/exchange/

Headers

Key Value
Authorization Bearer <TOKEN>

Request body:

Create Exchange JSON structured like this:

{
  "reference_id": "gvdags-abdvas-dabbwhf",
  "origin_amount": 50,
  "destination_amount": 100,
  "origin_currency_id": "MERIT-COIN",
  "destination_currency_id": "TOPSHERPA",
  "bonus_amount": 30,
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
  "destination_member": {
    "id": "member1",
    "first_name": "Example name"
  },
  "origin_member": {
    "id": "example_id"
  }
}
Field Description Type Required
reference_id Unique value string True
origin_amount int True
destination_amount int True
origin_currency_id Origin Currency ID string True
destination_currency_id Destination Currency ID string True
destination_member Member Object. Object consists of Supported Member Fields object True
origin_member Member Object. Object consists of data representing origin member object True
bonus_amount Bonus amount int False
campaign_id Campaign applied to transaction string False

Possible error codes

Response

Create Exchange JSON structured like this:

{
  "reference_id": "gvdags-abdvas-dabbwhf",
  "origin_amount": 50.0,
  "destination_amount": 100.0,
  "status": "IN_PROCESS",
  "status_code": "SUCCESS",
  "origin_currency_id": "MERIT-COIN",
  "destination_currency_id": "AL-FURSAN",
  "bonus_amount": 30,
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
  "total_amount": 130,
  "destination_transaction": {
    "reference_id": "328ccb61acd4ae6ce63b4ae34c42ebbe7f2f02d8"
  },
  "destination_member": {
    "id": "77250666"
  },
  "origin_member": {
    "id": "test"
  }
}
Field Description Type
reference_id Unique value string
origin_amount int
destination_amount int
status Transaction Status string
status_code Status Code string
origin_currency_id Origin Currency ID string
destination_currency_id Destination Currency ID string
destination_member Member Object. Object consists of Supported Member Fields object
origin_member Member Object. Object consists of data representing origin member object
bonus_amount Bonus amount int
campaign_id Campaign applied to transaction string
total_amount Total amount of received points int
destination_transaction Used for internal purposes at Merit. We do not recommend our clients to use the contents of this field. Provides the details relevant to the transaction at the destination loyalty program. Should be accessed only if the status of this transction is COMPLETED object

Get Exchange By reference_id

GET {host}/api/v1/exchanges/{reference_id}

Headers

Key Value
Authorization Bearer <TOKEN>

Path Parameters

Parameter Required Type Description
reference_id Yes String Client reference ID of the exchange transaction.

Possible error codes

Response

Get Exchange JSON structured like this:

{
  "reference_id": "gvdags-abdvas-dabbwhf",
  "origin_amount": 50.0,
  "destination_amount": 100.0,
  "status": "COMPLETED",
  "status_code": "SUCCESS",
  "origin_currency_id": "MERIT-COIN",
  "destination_currency_id": "AL-FURSAN",
  "bonus_amount": 30,
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
  "total_amount": 130,
  "destination_transaction": {
    "reference_id": "328ccb61acd4ae6ce63b4ae34c42ebbe7f2f02d8"
  },
  "destination_member": {
    "id": "77250666"
  },
  "origin_member": {
    "id": "test"
  }
}
Field Description Type
reference_id Unique value string
origin_amount int
destination_amount int
status Transaction Status string
status_code Status Code string
origin_currency_id Origin Currency ID string
destination_currency_id Destination Currency ID string
destination_member Member Object. Object consists of Supported Member Fields object
origin_member Member Object. Object consists of data representing origin member object
bonus_amount Bonus amount int
campaign_id Campaign applied to transaction str
total_amount Total amount of received points int
destination_transaction Used for internal purposes at Merit. We do not recommend our clients to use the contents of this field. Provides the details relevant to the transaction at the destination loyalty program. Should be accessed only if the status of this transction is COMPLETED object

Transaction

Field Description Type
id Transaction ID string
reference_id Unique value string
origin_amount int
destination_amount int
status Transaction Status string
status_code Status Code string
origin_currency_id Origin Currency ID string
destination_currency_id Destination Currency ID string
destination_member Member Object. Object consists of data taken from required_fields in LoyaltyProgram object
origin_member Member Object. Object consists of data representing origin member object
bonus_amount Bonus amount int
campaign_id Campaign applied to transaction str
total_amount Total amount of received points int

Exchange Webhooks

Integration Guide

Webhooks provide a powerful mechanism to receive real-time notifications about events occurring within your Merit account. By registering a webhook URL, Merit can directly push event data to your application, enabling immediate and automated responses.

Registering a Webhook URL

To begin receiving webhook notifications, provide your webhook URL to the Merit team. Merit will register this URL and subscribe it to relevant events based on your application needs. For instance, accounts using Merit's Points-Exchange APIs will have their webhooks subscribed to related transaction events.
Upon registration, a secret key will be shared with you, essential for the secure verification of incoming notifications.

Webhook Endpoint Requirements

Your endpoint must be a HTTPS POST endpoint listening on port 443.

Webhook Request Details

Payload Description
Body Each webhook notification contains an Event Object in JSON format.
Headers Critical headers include X-Merit-Signature, which is used for message verification.
{
  "Content-Type": "application/json",
  "X-Merit-Signature": "t=1492774577,v1=ansdoj213e9,v0=6ffbb59b"
}

Handling Webhook requests

Receiving and processing webhook notifications consists of two primary steps:

  1. Receipt of Message:
  1. Verification of Message:

import hmac
import hashlib
import base64
import re


def generate_base64_hmac(timestamp, raw_payload, secret_key):
  message = f"{timestamp},{raw_payload}"
  secret_key_bytes = str(secret_key).encode('utf-8')
  message_bytes = message.encode('utf-8')
  hmac_obj = hmac.new(secret_key_bytes, message_bytes, hashlib.sha256)
  hmac_digest = hmac_obj.digest()
  hmac_base64 = base64.b64encode(hmac_digest)
  return hmac_base64.decode()


def verify_signature(signature_header, raw_payload, secret_key):
  # Extracting timestamp
  timestamp_match = re.search(r't=(\d+)', signature_header)
  if not timestamp_match:
    return False
  timestamp = timestamp_match.group(1)
  computed_hash = generate_base64_hmac(timestamp, raw_payload, secret_key)
  # Extracting all hashes
  hashes = re.findall(r'v\d+=([^,]+)', signature_header)
  # Verifying each hash
  for expected_hash in hashes:
    if hmac.compare_digest(computed_hash, expected_hash):
      return True
  return False


# Example Use Case
signature_header = "t=1734111281,v1=yo7/S+zHFBg6sYWVcNkmb2j3EzIJ2XEQ0TlYWOaFWm0=,v1=T0kP94n+cL6Za34J8Y8nhAxpcdJxDVfXY5WxGo4Y1Ho="

raw_payload = '{"id":"evt_6820f736-5466-4a64-9858-ac8bf514f421","created_at":1686089970,"event_type":"points_exchange.transaction.created","data":{"id":"id_of_the_transaction","reference_id":"21212gvdags-abdvas-dabbwh12111212f","origin_amount":50,"destination_amount":100,"status":"COMPLETED","origin_currency_id":"MERIT-COIN","destination_currency_id":"AL-FURSAN","bonus_amount":30,"campaign_id":"56377294-572e-4155-b70a-c47867d6cd01","total_amount":130,"destination_member":{"id":"77250666"},"origin_member":{"id":"test"}}}'

secret_key = "0y1SVxadCBUY7MiACPEdPCJGD"

verification_result = verify_signature(signature_header, raw_payload, secret_key)

print("Verification successful." if verification_result else "Verification failed.")

Retry Policy

Merit attempts to deliver a given event to your webhook endpoint for up to 2 days with an exponential back-off. A max of 15 requests will be attempted.

Events

EventObject Example:

{
  "id": "evt_1NG8Du2eZvKYlo2CUI79vXWy",
  "created_at": 1686089970,
  "event_type": "transaction.completed",
  "version": "1.0",
  "data": {
    "id": "e38f42f7-474d-4720-872b-63b23e516314",
    "reference_id": "gvdags-abdvas-dabbwhf",
    "origin_amount": 50.0,
    "destination_amount": 100.0,
    "status": "COMPLETED",
    "status_code": "SUCCESS",
    "origin_currency_id": "MERIT-COIN",
    "destination_currency_id": "AL-FURSAN",
    "bonus_amount": 30,
    "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
    "total_amount": 130,
    "destination_member": {
      "id": "77250666"
    },
    "origin_member": {
      "id": "test"
    }
  }
}

Event object

name description
id Unique identifier of the event
created_at Unix epoch of the time at which this event is generated
event_type The type of the event. The entire list of event types can be found here EventTypes
version The version of the resource object which is available in the data field. This will be of no significance unless there is a version change which impacts the data structure.
data Object containing the data associated with the event. The structure of the object depends on the event_type. The details of the structure can be found in Transaction

Event Types

This is a list of all the types of events we currently send. We may add more at any time, so in developing and maintaining your code, you should not assume that only these types exist.

name description
points_exchange.transaction.completed Occurs when a points-exchange transaction is completed.
points_exchange.transaction.failed Occurs when a points-exchange transaction is failed

EARN API

The EARN API enables partners to award points to customers for eligible purchases across supported loyalty programs.

For EARN authentication, see Authorization (Client Credentials Flow). For pre-transaction member checks and accepted member attributes, see Member Validation and Supported Member Fields.

List EARN partners

GET {host}/api/v1/loyalty-programs/{currency_id}/earn-partners?limit=10&offset=0

This API lists all partner loyalty programs into whose user accounts a Client can add points, based on spending made on the Client's platform, including web, app, and POS channels.

The response follows the same structure as List Exchange Out Loyalty Programs.

Headers

Key Value
Accept-Language en
Authorization Bearer <TOKEN>

Path Parameters

Parameter Required Type Description
currency_id Yes String The currency ID of a loyalty program that belongs to the Client. If the Client does not have its own loyalty program, Merit creates one because it is required by the system. The Client can retrieve this ID using List my loyalty programs.

Query Parameters

Parameter Required Type Description
limit No Integer Maximum number of records; defaults to 10 and cannot exceed 100.
offset No Integer Number of records to skip; defaults to 0.

RESPONSE - 200 OK

[
  {
    "loyalty_program_name": "wala-one",
    "currency_id": "WALA_ONE",
    "currency_name": "wala-one points",
    "logo_url": "https://cdn.example.com/wala-one/logo.svg",
    "banner_url": "https://cdn.example.com/wala-one/banner.jpg",
    "category": "Retail",
    "brand": "wala-one",
    "description": "Earn wala-one points on eligible purchases.",
    "terms_and_conditions": "Partner terms apply.",
    "required_fields": [
      {
        "id": "mobile_number",
        "name": "Mobile number"
      },
      {
        "id": "id",
        "name": "Membership ID"
      }
    ],
    "validation_assurance": "CONFIRMED",
    "partnership_details": {
      "partnership_type": "FIAT_TO_POINTS",
      "origin_fiat_currency": "SAR",
      "origin_currency_id": "MY_LOYALTY_PROGRAM",
      "origin_to_destination_rate": "0.600000",
      "origin_min_amount": "10.00",
      "origin_max_amount": "10000.00",
      "destination_min_amount": 6,
      "destination_max_amount": 5000,
      "reversal_supported": true,
      "reversal_window_days": 14
    },
    "campaign": null
  }
]

EARN partner loyalty program

Field Description Type
loyalty_program_name Localized loyalty program display name string
currency_id The currency ID of the loyalty program string
currency_name Localized destination points currency name string
logo_url Loyalty program logo URL string
banner_url Loyalty program banner URL string
category Loyalty program business category string
brand Loyalty program brand string
description Localized EARN description string
terms_and_conditions Localized EARN terms and conditions string
required_fields Required member fields. See Supported Member Fields. object
validation_assurance Validation reliability. See Validation Assurance. string
partnership_details EARN partnership rate and transaction limits for this loyalty program object
partnership_details.partnership_type Always FIAT_TO_POINTS for EARN partners string
partnership_details.origin_fiat_currency ISO 4217 currency in which the client transaction is priced string
partnership_details.origin_currency_id Origin loyalty-program currency ID from which this EARN partnership is available string
partnership_details.origin_to_destination_rate Destination points earned for one unit of origin_fiat_currency; returned as an exact decimal string decimal string
partnership_details.origin_min_amount Minimum eligible transaction value in the origin fiat currency decimal string
partnership_details.origin_max_amount Maximum eligible transaction value in the origin fiat currency decimal string
partnership_details.destination_min_amount Minimum points that may be earned in one transaction integer
partnership_details.destination_max_amount Maximum points that may be earned in one transaction integer
partnership_details.reversal_supported Whether this EARN partnership accepts transaction reversals boolean
partnership_details.reversal_window_days Number of days within which a reversal is allowed; applicable when reversal is supported integer
campaign Active Campaign configuration, or null when no campaign applies object

Calculate EARN

POST {host}/api/v1/calculate-earn/

Calculates the number of points that will be credited to the destination loyalty program's account for a given fiat spend.

Headers

Key Value
Authorization Bearer <TOKEN>

Request body

{
  "origin_amount": "1000.00",
  "origin_fiat_currency": "SAR",
  "origin_currency_id": "MY_LOYALTY_PROGRAM",
  "destination_currency_id": "WALA_ONE",
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
Field Description Type Required
origin_amount Eligible transaction value in the origin currency decimal string true
origin_fiat_currency ISO 4217 transaction currency returned by EARN partner discovery string true
origin_currency_id Origin loyalty-program currency ID used to select the EARN partnership string true
destination_currency_id Destination points currency ID returned by EARN partner discovery string true
campaign_id Active campaign applied to the calculation string false

Possible error codes

Response

{
  "origin_amount": "1000.00",
  "destination_amount": 600,
  "bonus_amount": 0,
  "total_amount": 600,
  "origin_fiat_currency": "SAR",
  "origin_currency_id": "MY_LOYALTY_PROGRAM",
  "destination_currency_id": "WALA_ONE",
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01"
}
Field Description Type
origin_amount Transaction value used for the calculation decimal string
destination_amount Base destination points before campaign bonus integer
bonus_amount Additional points earned from the campaign integer
total_amount Total points: destination_amount + bonus_amount integer
origin_fiat_currency Origin ISO 4217 transaction currency string
origin_currency_id Origin loyalty-program currency ID used by the EARN partnership string
destination_currency_id Destination points currency ID receiving the points string
campaign_id Applied campaign ID, or null string

The rate, campaign, FX conversion, limits, and rounding are evaluated again when the EARN transaction is posted.

Post an EARN transaction

POST {host}/api/v1/earn/

Adds points to the destination loyalty program. The endpoint follows Create Exchange, with a fiat origin and EARN-specific channel and reporting fields.

This API operates asynchronously. It accepts the request and responds after validating the partner, member fields, amounts, campaign, and client funding. Merit then submits the accrual to the partner. Clients receive the final status by webhook or through the EARN status endpoint.

Headers

Key Value
Authorization Bearer <TOKEN>

Request body

{
  "reference_id": "ORDER-10492",
  "origin_fiat_amount": "1000.00",
  "destination_amount": 600,
  "origin_fiat_currency": "SAR",
  "origin_currency_id": "MY_LOYALTY_PROGRAM",
  "destination_currency_id": "WALA_ONE",
  "bonus_amount": 0,
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
  "destination_member": {
    "mobile_number": "+966555555555"
  },
  "category": "ELECTRONICS",
  "branch_id": "RIYADH-01",
  "note": "Fulfilled order"
}
Field Description Type Required
reference_id Unique value string true
origin_fiat_amount Eligible transaction value in the origin fiat currency decimal string true
destination_amount integrer true
origin_fiat_currency ISO 4217 transaction currency string true
origin_currency_id Origin Currency ID string true
destination_currency_id Destination Currency ID string true
destination_member Member Object. Object consists of Supported Member Fields object true
bonus_amount Bonus amount integer false
campaign_id Campaign applied to the transaction string false
category Client transaction category. Maximum 100 characters; allowed characters are letters, numbers, _, and -. string false
branch_id Client branch identifier. Maximum 100 characters; allowed characters are letters, numbers, _, and -. string false
note Optional PII-free transaction note; maximum 500 characters string false

Possible error codes

Response

{
  "id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
  "transaction_operation": "CREDIT",
  "parent_transaction_id": null,
  "reference_id": "ORDER-10492",
  "origin_fiat_amount": "1000.00",
  "destination_amount": 600,
  "status": "IN_PROCESS",
  "status_code": "SUCCESS",
  "origin_fiat_currency": "SAR",
  "origin_currency_id": "MY_LOYALTY_PROGRAM",
  "destination_currency_id": "WALA_ONE",
  "bonus_amount": 0,
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
  "total_amount": 600,
  "destination_transaction": {
    "reference_id": null
  },
  "destination_member": {
    "mobile_number": "+966******555"
  },
  "category": "ELECTRONICS",
  "branch_id": "RIYADH-01",
  "note": "Fulfilled order"
}
Field Description Type
id Merit transaction ID used for reversals and internal traceability string
transaction_operation EARN operation represented by the transaction: CREDIT or REVERSAL string
parent_transaction_id Original EARN transaction ID for a reversal; null for a credit transaction string or null
reference_id Unique value string
origin_fiat_amount Original fiat transaction value decimal string
destination_amount integer
status Transaction Status string
status_code Status Code string
origin_fiat_currency Origin ISO 4217 transaction currency string
origin_currency_id Origin Currency ID string
destination_currency_id Destination Currency ID string
bonus_amount Bonus amount integer
campaign_id Campaign applied to transaction string
total_amount Total amount of received points integer
destination_transaction Used for internal purposes at Merit. We do not recommend our clients to use the contents of this field. Provides the details relevant to the transaction at the destination loyalty program. Should be accessed only if the status of this transction is COMPLETED object
destination_member Member Object. Object consists of Supported Member Fields object
category Client transaction category string
branch_id Client branch identifier. string
note string

Clients must integrate reversals even if they post at fulfillment. For e-commerce, Merit recommends calculating at checkout and posting at fulfillment. For POS, Merit recommends posting when the sale completes.

Get an EARN transaction

GET {host}/api/v1/earn/transactions/{transaction_id}

Returns a credit or reversal owned by the authenticated client. Polling is a fallback for clients that cannot consume webhooks.

Headers

Header Required Value
Authorization Yes Bearer <TOKEN>

Path Parameters

Parameter Required Type Description
transaction_id Yes String Merit transaction ID returned in the id field of the Post EARN transaction response.

Possible error codes

RESPONSE - 200 OK

{
  "id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
  "transaction_operation": "CREDIT",
  "parent_transaction_id": null,
  "reference_id": "ORDER-10492",
  "origin_fiat_amount": "1000.00",
  "destination_amount": 600,
  "status": "COMPLETED",
  "status_code": "SUCCESS",
  "origin_fiat_currency": "SAR",
  "origin_currency_id": "MY_LOYALTY_PROGRAM",
  "destination_currency_id": "WALA_ONE",
  "bonus_amount": 0,
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
  "total_amount": 600,
  "destination_transaction": {
    "reference_id": "WALA-849201"
  },
  "destination_member": {
    "mobile_number": "+966******555"
  },
  "category": "ELECTRONICS",
  "branch_id": "RIYADH-01",
  "note": "Fulfilled order"
}

The response uses the same fields as the response of Post an EARN transaction. For a completed transaction, destination_transaction.reference_id contains the partner transaction reference.

Reverse an EARN transaction

POST {host}/api/v1/earn/transactions/{transaction_id}/reversals

Reverses points from a completed EARN transaction. Partial reversals are not supported at the moment. To keep the API flexible, we accept an origin_amount parameter so it can be extended to partial reversals in the future. For now, pass the full amount of the parent transaction you want to reverse. A pending EARN credit must reach a terminal state before it can be reversed. The endpoint is available only when partner discovery returns reversal_supported: true, and the request must be submitted within the partnership's reversal_window_days.

Headers

Key Value
Authorization Bearer <TOKEN>

Path Parameters

Parameter Required Type Description
transaction_id Yes String Merit ID of the original completed EARN credit to reverse.

Request body

{
  "reference_id": "REFUND-10492-1",
  "origin_amount": "250.00",
  "origin_fiat_currency": "SAR",
  "note": "One item returned"
}
Field Description Type Required
reference_id Client-generated unique reversal reference.

This should be a new reference_id different from the reference_id of the parent transaction.
string true
origin_amount Refunded amount in the original transaction currency decimal string true
origin_fiat_currency ISO 4217 transaction currency returned by EARN partner discovery string true
note Optional PII-free explanation; maximum 500 characters string false

origin_amount must be positive and cannot exceed the original transaction's remaining reversible amount. Merit computes the points to reverse from the original transaction pricing.

Possible error codes

Response

{
  "id": "erv_01K0XB1F1GCXQTX42G2Z19F09Y",
  "transaction_operation": "REVERSAL",
  "parent_transaction_id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
  "reference_id": "REFUND-10492-1",
  "origin_amount": "250.00",
  "destination_amount": 150,
  "status": "IN_PROCESS",
  "status_code": "SUCCESS",
  "origin_fiat_currency": "SAR",
  "origin_currency_id": "MY_LOYALTY_PROGRAM",
  "destination_currency_id": "WALA_ONE",
  "bonus_amount": 0,
  "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
  "total_amount": 150,
  "destination_transaction": {
    "reference_id": null
  },
  "destination_member": {
    "mobile_number": "+966******555"
  },
  "category": "ELECTRONICS",
  "branch_id": "RIYADH-01",
  "note": "Fulfilled order"
}
Field Description Type
id Merit transaction ID for the reversal string
transaction_operation Transaction operation; always REVERSAL for this endpoint string
parent_transaction_id Merit transaction ID of the original EARN credit being reversed string
reference_id Client-generated unique reference ID for the reversal string
origin_amount Full fiat amount of the parent transaction submitted for reversal decimal string
destination_amount Number of base points to reverse, represented as a string with two decimal places decimal string
status Transaction Status string
status_code Status Code string
origin_fiat_currency ISO 4217 currency of the parent transaction's fiat amount string
origin_currency_id Origin Currency ID from the parent transaction string
destination_currency_id Destination Currency ID from the parent transaction integer
bonus_amount Bonus points to reverse integer
campaign_id Campaign applied to the parent transaction, or null string
total_amount Total points to reverse, including bonus points integer
destination_transaction Partner reversal transaction details; reference_id may be null until completion object
destination_member Member Object. Object consists of Supported Member Fields object
category Client transaction category string
branch_id Client branch identifier. string
note string

Reversal points are calculated using the original transaction's ratio, promotion multiplier, FX snapshot, and rounding rule. Current pricing is never used.

Reversals outside the configured partner window return 400 REVERSAL_WINDOW_EXPIRED and are routed for manual support.

HTTP errors use the existing {code, message, details} envelope:

Error code HTTP Meaning
VALIDATION_ERROR 400 Request shape or field value is invalid
DUPLICATE_REFERENCE_ID 400 The client reference_id has already been used
INCORRECT_DESTINATION_AMOUNT 400 Destination amount does not match Merit's calculation
INCORRECT_BONUS_AMOUNT 400 Bonus amount does not match the selected campaign
CAMPAIGN_NOT_AVAILABLE 400 Campaign does not exist, is inactive, or is unavailable
MIN_AMOUNT_LIMIT_NOT_REACHED 400 Transaction amount is below the configured minimum
MAX_AMOUNT_LIMIT_REACHED 400 Transaction amount exceeds the configured maximum
INCORRECT_MEMBER 400 Required member data is missing or incorrect
TRANSACTION_NOT_REVERSIBLE 400 Transaction state or partnership configuration prevents reversal
REVERSAL_AMOUNT_EXCEEDED 400 Requested reversal exceeds the remaining reversible amount
REVERSAL_WINDOW_EXPIRED 400 Partner reversal window elapsed
UNAUTHORIZED 401 Bearer token is missing or invalid
ACCESS_DENIED 403 Client does not have access to the requested resource
HAVE_NO_ACCESS_TO_LOYALTY_PROGRAM 403 Client does not have access to the requested loyalty program
NOT_FOUND 404 Loyalty program or transaction was not found
METHOD_NOT_ALLOWED 405 HTTP method is not supported for the endpoint
INTERNAL_ERROR 500 Unexpected Merit failure

EARN webhooks

Credits and refunds use the same webhook event types and transaction body. Inspect transaction_operation to identify the operation: CREDIT means points were earned and REVERSAL means points were refunded. Credit events have parent_transaction_id: null; refund events contain parent_transaction_id with the original credit transaction ID. Events may be delivered more than once or arrive out of order. Deduplicate by event id, acknowledge with HTTP 2xx, and apply an event only when it represents a newer resource state. Retrieve the transaction when local state is uncertain. The event idempotency_key identifies webhook delivery; it is unrelated to the request Idempotency-Key.

EARN event types

Event type Description
earn.transaction.completed An EARN credit or refund completed successfully
earn.transaction.failed An EARN credit or refund failed

EARN EVENT EXAMPLE

{
  "id": "evt_01K0XC93A1MXCKF9QN6T5Z0G7Z",
  "created_at": 1784727063,
  "event_type": "earn.transaction.completed",
  "version": "1.0",
  "idempotency_key": "delivery_01K0XC93B8R4P9RZRJ2Q3V1JRA",
  "data": {
    "id": "etx_01K0X9V65AB8KE2B7JTZQ5QG7V",
    "transaction_operation": "CREDIT",
    "parent_transaction_id": null,
    "reference_id": "ORDER-10492",
    "origin_fiat_amount": "1000.00",
    "destination_amount": 600,
    "status": "COMPLETED",
    "status_code": "SUCCESS",
    "origin_fiat_currency": "SAR",
    "origin_currency_id": "MY_LOYALTY_PROGRAM",
    "destination_currency_id": "WALA_ONE",
    "bonus_amount": 0,
    "campaign_id": "56377294-572e-4155-b70a-c47867d6cd01",
    "total_amount": 600,
    "destination_transaction": {
      "reference_id": "WALA-849201"
    },
    "destination_member": {
      "mobile_number": "+966******555"
    },
    "category": "ELECTRONICS",
    "branch_id": "RIYADH-01",
    "note": "Fulfilled order"
  }
}