Rewards catalog

Define what members can redeem points for.

View as MarkdownInstall skills

The rewards catalog is the menu of everything a member can spend points on — a percentage discount, a free product, store credit, or a partner perk. Each entry declares what it costs, when it is available, and who qualifies, so redemption can validate and issue it without bespoke logic.

The reward objectAsk

A reward (prefix rwd_) is a reusable catalog template. Redeeming one produces a redemption (prefix rdm_) — the record of a specific member spending points at a specific time. Rewards live in your program, are referenced at checkout, and emit events when redeemed.

Prop

Type

import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

// A 500-point reward worth EUR 5.00 off the cart.
const reward = await vinr.loyalty.rewards.create({
  program: 'prog_default',
  name: '€5 off your order',
  type: 'discount_amount',
  cost: 500,
  value: { amount: 500, currency: 'EUR' },   // minor units
});                                            // "rwd_..."

Reward typesAsk

The type field selects how the reward applies once redeemed. The value payload differs per type.

Typevalue shapeApplied as
discount_percent{ percent: 10 }Percent off the eligible cart subtotal.
discount_amount{ amount: 500, currency: 'EUR' }Fixed minor-unit discount at checkout.
store_credit{ amount: 1000, currency: 'EUR' }Balance added to the linked customer.
free_product{ product: 'prod_...', quantity: 1 }A catalog product added at zero cost.
partner{ partner: 'acme', sku: 'GIFT20' }An external fulfillment reference VINR records but does not ship.

discount_percent and discount_amount redemptions return a discount token your checkout passes to the payment so the member sees the reduction at the moment of paying.

Cost & availabilityAsk

cost is always expressed in points, never currency. The relationship between points and money is set by your earning rules — VINR does not assume a fixed conversion. Two controls gate availability:

  • active — a master switch. Flip to false to retire a reward without deleting its history.
  • inventory — optional scarcity. Each successful redemption decrements it; at 0 the reward is shown but cannot be redeemed. Use null for unlimited.
// Limited drop: 200 units, then it sells out.
await vinr.loyalty.rewards.update('rwd_limited', {
  active: true,
  inventory: 200,
});

You can also window availability with availableFrom and availableUntil ISO-8601 timestamps for seasonal or campaign-bound rewards.

Eligibility rulesAsk

Beyond cost and inventory, an optional eligibility block restricts who can redeem. Rules are evaluated at redemption time against the member's state; failing any rule returns a reward_not_eligible error.

await vinr.loyalty.rewards.update('rwd_goldperk', {
  eligibility: {
    minTier: 'gold',                 // see Tiers & status
    firstTimeOnly: false,
    maxPerMember: 2,                  // lifetime cap
    minAccountAgeDays: 30,
  },
});
RuleEffect
minTierMember must be at or above this tier.
maxPerMemberLifetime redemption cap per loyalty_account.
firstTimeOnlyRedeemable only if the member has never redeemed it.
minAccountAgeDaysGuards against same-day signup abuse.

Eligibility is enforced server-side on every redemption. Never gate rewards purely in your UI — a member can call the API directly, and only the VINR check is authoritative.

Managing the catalogAsk

List, filter, and archive rewards as your program evolves. Listing supports filtering by active and type.

// Show the live menu a member could browse.
const { data } = await vinr.loyalty.rewards.list({
  program: 'prog_default',
  active: true,
});

for (const r of data) {
  console.log(`${r.name} — ${r.cost} pts`);
}

Prefer active: false over deletion: redemptions reference their reward, and archiving preserves that audit trail. Catalog changes emit loyalty.reward.created and loyalty.reward.updated; redemptions emit loyalty.reward.redeemed. Subscribe to keep downstream systems (analytics, fulfillment) in sync — verify each delivery with vinr.webhooks.verify(payload, signature).

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page