Complete REST API documentation for customer loyalty, points, vouchers, and rewards management. Integrate loyalty functionality into your POS, mobile app, or e-commerce platform.
The KiriMel Loyalty API allows you to programmatically manage customer loyalty programs, including registration, points earning/redemption, voucher management, and rewards. Built for seamless integration with POS systems like VectorPOS (VectorDine and VectorRetail).
Get started in 3 steps: 1) Create a loyalty group, 2) Add your sites, 3) Register customers and start awarding points. See the endpoints below for details.
All Loyalty API endpoints require an API key with HMAC authentication. Generate your API keys in the Loyalty Dashboard after creating your loyalty group.
Need step-by-step integration examples? Check out our Loyalty API Workflows page for complete POS integration scenarios including member checkout, voucher redemption, and points payment.
The Loyalty API uses HMAC-SHA256 signature authentication for security. Each request must include three headers:
X-API-Key: your_api_key_here
X-Timestamp: 2024-01-01T12:00:00Z
X-Signature: computed_hmac_signature_here
The signature is computed as:
signature = HMAC-SHA256(timestamp + requestBody, apiSecret)
$timestamp = gmdate('Y-m-d\TH:i:s\Z');
$signature = hash_hmac('sha256', $timestamp . $requestBody, $apiSecret);
$headers = [
'X-API-Key: ' . $apiKey,
'X-Timestamp: ' . $timestamp,
'X-Signature: ' . $signature
];
const crypto = require('crypto');
const timestamp = new Date().toISOString();
const signature = crypto.createHmac('sha256', apiSecret)
.update(timestamp + requestBody)
.digest('hex');
import hmac
import hashlib
import time
timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
signature = hmac.new(apiSecret.encode(), (timestamp + body).encode(), hashlib.sha256).hexdigest()
API requests are rate-limited per API key to ensure fair usage:
Rate limit headers are included in every response:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704067200
Creates a new loyalty group (company) with an associated tenant database. Each merchant can have one loyalty group containing multiple sites.
/group/create
| Parameter | Type | Required | Description |
|---|---|---|---|
company_slug |
string | Required | URL-safe company identifier (lowercase letters and numbers only, min 3 chars) |
company_name |
string | Required | Legal company name |
business_type |
string | Required | RESTAURANT or RETAIL |
curl -X POST http://localhost/api/loyalty/group/create \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here" \
-d "company_slug=myrestaurant&company_name=My+Restaurant+Sdn+Bhd&business_type=RESTAURANT"
{
"success": true,
"group_id": 123,
"group_code": "myrestaurant_a1b2c3d4",
"tenant_db": "kiriloyalty_myrestaurant_a1b2c3d4"
}
Add a new location/site to an existing loyalty group. A loyalty group can have multiple sites (e.g., main restaurant, branch outlet).
/group/sites/add
| Parameter | Type | Required | Description |
|---|---|---|---|
site_code |
string | Required | Unique site code (uppercase letters and numbers only, min 3 chars) |
site_name |
string | Required | Display name for the location |
site_type |
string | Required | RESTAURANT or RETAIL |
address |
string | Optional | Street address |
city |
string | Optional | City |
state |
string | Optional | State/province |
postal_code |
string | Optional | Postal code |
phone |
string | Optional | Contact phone (include country code) |
curl -X POST http://localhost/api/loyalty/group/sites/add \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here" \
-d "site_code=MAIN01&site_name=Main+Restaurant&site_type=RESTAURANT&address=123+Main+St&city=Kuala+Lumpur&phone=%2B60123456789"
{
"success": true,
"site_id": 456,
"site_code": "MAIN01"
}
Register a new customer in the loyalty program. Customers can be identified by phone number or QR code.
/customers/register
| Parameter | Type | Required | Description |
|---|---|---|---|
phone |
string | Conditional* | Phone number with country code (e.g., +60123456789) |
qr_code |
string | Conditional* | QR code string |
name |
string | Optional | Customer name |
email |
string | Optional | Email address |
birth_date |
string | Optional | Birthday for birthday rewards (YYYY-MM-DD format) |
*Either phone or qr_code must be provided
curl -X POST http://localhost/api/loyalty/customers/register \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here" \
-d "phone=%2B60123456789&name=John+Doe&birth_date=1990-05-15"
{
"success": true,
"customer_id": "CUST_123456",
"wallet_id": "WALLET_789",
"points_balance": 0,
"tier": "BRONZE"
}
Find an existing customer by phone number, QR code, or promo code. Returns customer profile with current points balance.
/customers/lookup
| Parameter | Type | Required | Description |
|---|---|---|---|
phone |
string | Conditional* | Phone number with country code |
identifier |
string | Conditional* | QR code or promo code |
*At least one parameter must be provided
curl -X GET "http://localhost/api/loyalty/customers/lookup?phone=%2B60123456789" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here"
{
"success": true,
"data": {
"id": 123,
"site_id": 456,
"phone": "+60123456789",
"name": "John Doe",
"email": "[email protected]",
"status": "ACTIVE",
"balance_points": 1500,
"joined_at": "2026-05-01T10:00:00Z",
"last_earn_at": "2026-05-10T12:00:00Z",
"last_redeem_at": "2026-05-09T15:30:00Z",
"last_seen_at": "2026-05-10T14:00:00Z"
}
}
Find an existing customer by email address. Returns customer profile with current points balance.
/customers/lookup-by-email
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string | Required | Email address to lookup |
curl -X GET "http://localhost/api/loyalty/customers/lookup-by-email?email=john%40example.com" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here"
{
"success": true,
"data": {
"id": 123,
"site_id": 456,
"phone": "+60123456789",
"name": "John Doe",
"email": "[email protected]",
"status": "ACTIVE",
"balance_points": 1500,
"joined_at": "2026-05-01T10:00:00Z",
"last_earn_at": "2026-05-10T12:00:00Z",
"last_redeem_at": "2026-05-09T15:30:00Z",
"last_seen_at": "2026-05-10T14:00:00Z"
}
}
Award points to a customer's wallet after a purchase or action. Points are calculated based on your configured conversion rate.
/points/earn
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
string | Required | Customer ID from registration |
points |
integer | Required | Points to award (must be positive integer) |
amount |
decimal | Required | Purchase amount (for record-keeping, e.g., 25.50) |
reference_id |
string | Optional | External reference ID for idempotency (e.g., order ID, transaction ID) |
description |
string | Optional | Description of the points transaction |
curl -X POST http://localhost/api/loyalty/points/earn \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here" \
-d "customer_id=CUST_123456&points=50&amount=25.50&reference_id=ORDER_123&description=Dining+reward"
{
"success": true,
"transaction_id": "TXN_456",
"points_awarded": 50,
"new_balance": 150,
"tier_progress": {
"current_tier": "BRONZE",
"points_to_next": 850,
"next_tier": "SILVER"
}
}
Redeem points from a customer's wallet for rewards or discounts. Validates sufficient balance before processing.
/points/redeem
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
string | Required | Customer ID |
points |
integer | Required | Points to redeem (must be positive integer) |
reference_id |
string | Optional | External reference ID for idempotency |
description |
string | Optional | Redemption description |
curl -X POST http://localhost/api/loyalty/points/redeem \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here" \
-d "customer_id=CUST_123456&points=100&reference_id=BILL_456&description=Discount+redemption"
{
"success": true,
"transaction_id": "TXN_789",
"points_redeemed": 100,
"new_balance": 50
}
Retrieve a customer's current points balance, tier status, and expiry information.
/wallet/balance
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
string | Required | Customer ID from registration |
curl -X GET "http://localhost/api/loyalty/wallet/balance?customer_id=CUST_123456" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here"
{
"success": true,
"customer_id": "CUST_123456",
"balance": 150,
"tier": {
"name": "SILVER",
"benefits": ["5% discount on birthdays", "Priority seating"]
},
"points_expiring_soon": 20,
"expiry_date": "2024-06-30"
}
Generate a batch of unique vouchers for distribution. Each voucher has a unique 12-character code.
/vouchers/batches/create
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Required | Batch name (e.g., "Grand Opening Promo") |
type |
string | Required | Voucher type: PERCENT, FIXED, or BOGO |
value |
decimal | Required | Voucher value (percentage or amount) |
quantity |
integer | Required | Number of vouchers to generate (max 1000 per batch) |
valid_from |
string | Optional | Valid from date (YYYY-MM-DD format) |
valid_until |
string | Optional | Valid until date (YYYY-MM-DD format) |
min_purchase |
decimal | Optional | Minimum purchase amount for voucher use |
curl -X POST http://localhost/api/loyalty/vouchers/batches/create \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here" \
-d "name=Grand+Opening+Promo&type=PERCENT&value=10&quantity=100&valid_from=2024-06-01&valid_until=2024-12-31&min_purchase=50"
{
"success": true,
"batch_id": "BATCH_001",
"vouchers_created": 100,
"voucher_codes": ["VOUCHER_A1B2C3D4E5F6", "VOUCHER_G7H8I9J0K1L2", ...]
}
Redeem a voucher code, applying the discount or reward to the customer's transaction. Validates voucher expiry and usage status.
/vouchers/redeem
| Parameter | Type | Required | Description |
|---|---|---|---|
code |
string | Required | Voucher code (12-character base62 string) |
customer_id |
string | Required | Customer ID redeeming the voucher |
purchase_amount |
decimal | Optional | Purchase amount for validation (for vouchers with min_purchase) |
reference_id |
string | Optional | External reference ID (e.g., order ID, transaction ID) |
curl -X POST http://localhost/api/loyalty/vouchers/redeem \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: your_api_key" \
-H "X-Timestamp: 2024-01-01T12:00:00Z" \
-H "X-Signature: signature_here" \
-d "code=VOUCHER_A1B2C3D4E5F6&customer_id=CUST_123456&purchase_amount=75.00&reference_id=ORDER_456"
{
"success": true,
"redemption_id": "REDEEM_123",
"voucher_type": "PERCENT",
"voucher_value": 10,
"discount_applied": 7.50,
"new_purchase_amount": 67.50
}
The API uses standard HTTP response codes and returns detailed error information in JSON format.
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Customer does not have enough points to complete this redemption",
"details": {
"requested": 100,
"available": 50,
"shortfall": 50
}
}
}
| Code | Description |
|---|---|
200 |
Request successful |
400 |
Bad Request - Invalid parameters |
401 |
Unauthorized - Invalid API key or signature |
404 |
Not Found - Resource doesn't exist |
429 |
Too Many Requests - Rate limit exceeded |
500 |
Internal Server Error |
| Error Code | Description |
|---|---|
INVALID_API_KEY |
API key is invalid or expired |
SIGNATURE_INVALID |
HMAC signature verification failed |
CUSTOMER_NOT_FOUND |
Customer ID does not exist |
INSUFFICIENT_BALANCE |
Not enough points in wallet |
VOUCHER_EXPIRED |
Voucher has passed expiry date |
VOUCHER_ALREADY_REDEEMED |
Voucher has already been used |
VOUCHER_NOT_FOUND |
Voucher code does not exist |
SITE_LIMIT_REACHED |
Maximum number of sites reached for your plan |