API Reference

The LoyaltyPro REST API follows standard HTTP conventions. All responses are JSON. Errors follow RFC 7807 Problem Details format.

Base URL

BASH
https://loyalty-engine-production-e5cb.up.railway.app/v1

All endpoints are versioned under /v1. Monetary amounts are always in cents — R150.00 = 15000. UUIDs throughout.

Authentication

Pass your API key in X-API-Key, or a JWT in Authorization: Bearer. Test keys (sk_test_*) enable debug hints and request logs. Live keys (sk_live_*) are for production.

BASH
# API Key (server-to-server, POS)
curl "https://loyalty-engine-production-e5cb.up.railway.app/v1/tenants/$TENANT_ID/members" \
  -H "X-API-Key: sk_test_YOUR_KEY"

# JWT Bearer (merchant portal)
curl "https://loyalty-engine-production-e5cb.up.railway.app/v1/tenants/$TENANT_ID/members" \
  -H "Authorization: Bearer YOUR_JWT"

Conventions

AmountsAlways in cents (smallest currency unit). R15.00 = 1500.
DatesISO 8601 / RFC 3339. Example: 2026-01-15T10:30:00.000Z
IDsUUID v4 throughout.
PaginationKeyset cursor — pass after=<nextCursor>. nextCursor: null = last page.
Rate limit100 req/min per tenant. 429 with Retry-After header on breach.
Body size256 KB maximum.

Idempotency

POST /transactions/evaluate and POST /transactions/redeem are fully idempotent. Pass the same transactionRef and you receive the original result — never a duplicate award or double-deduction. Safe to retry on network failures.

Error format

All errors return a RFC 7807 Problem Details object:

JSON
{
  "type":      "https://loyalty-engine.yoyo.co.za/errors/insufficient-points",
  "title":     "Insufficient Points",
  "status":    422,
  "detail":    "Member only has 300 points; 500 requested.",
  "requestId": "req_a1b2c3d4-...",
  "context":   { "available": 300, "requested": 500 }
}

Error codes

StatusCodeDescription
400VALIDATION_ERRORInvalid request body or missing required field
401UNAUTHORIZEDMissing or invalid API key / JWT
403FORBIDDENAPI key does not have access to this tenant
403MEMBER_SUSPENDEDMember account has been suspended
404NOT_FOUNDTenant, member, campaign or resource does not exist
409TRANSACTION_DUPLICATEtransactionRef already processed — idempotent result returned
422INSUFFICIENT_POINTSMember balance is below the amount requested
429RATE_LIMITED100 req/min per tenant exceeded — check Retry-After header
500INTERNAL_ERRORUnexpected server error — safe to retry

Earn at checkout — points & rewards

cURL
curl -X POST "https://loyalty-engine-production-e5cb.up.railway.app/v1/tenants/$TENANT_ID/transactions/evaluate" \
  -H "X-API-Key: sk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionRef": "receipt_001",
    "memberRef":      "customer_27821234567",
    "amount":         15000
  }'

Response

200 OK
{
  "transactionId":  "d2f3a1b4-0e5c-4a8b-9f1d-2c3e4f5a6b7c",
  "transactionRef": "receipt_001",
  "memberId":       "9f4c2e8a-1b2c-3d4e-5f6a-7b8c9d0e1f2a",
  "memberCreated":  true,
  "pointsAwarded":  150,
  "pointsBalance":  150,
  "campaigns": [
    {
      "campaignId":   "5a7b3c9d-...",
      "campaignName": "Spend & Earn",
      "pointsEarned": 150,
      "rewardIds":    []
    }
  ],
  "state": "PROCESSED"
}
API Reference — LoyaltyPro