</> Developers

Record loyalty activity. Manage reward redemption.

Purchase events record points and stamps synchronously and return the member’s balance. Creating a discount or store-credit reward adds a merchant catalog item; a separate redemption records a member’s use of it. Provider gift-card payout remains unavailable.

Bearer-key auth, idempotent writes, HMAC-signed webhooks. Plain JSON over HTTPS, no SDK.

Start in 60 seconds

Three steps take you from a new key to a recorded purchase. Keep the key in an environment variable or secret store, not in code.

  1. Create a key. Sign in and open Dashboard → Settings → Team & access. Copy the owner key when it is shown; it is stored only as a SHA-256 hash and cannot be shown again. An owner key also opens your dashboard, so treat it like a password and never paste it into an AI chat.
  2. Check it with GET /v1/me. The response names the business, the key’s role and what it may do, so a wrong or revoked key fails here instead of halfway through a sale.
  3. Record a purchase. Enroll the customer once with POST /v1/members (idempotent by email), then send each sale with your own order identifier as externalId. A purchase for an email that is not enrolled returns 404 not_found.

Step 2 · curl

export REWARDFINITY_KEY="lp_sk_…"   # shown once when you create it

curl https://api.rewardfinity.com/v1/me \
  -H "authorization: Bearer $REWARDFINITY_KEY"

Response

{
  "merchant": { "id": "mch_…", "name": "Corner Coffee", "slug": "corner-coffee-…", "currency": "CAD", "country": "CA" },
  "key": { "id": "key_…", "role": "owner", "label": "Till integration" },
  "permissions": [
    "merchant:manage", "members:read", "members:write", "events:purchase",
    "rewards:redeem", "program:manage", "campaigns:manage", "integrations:manage",
    "reports:read"
  ],
  "docs": "https://rewardfinity.com/developers",
  "mcp": "https://api.rewardfinity.com/mcp"
}

A staff key returns the same shape with "role": "scanner" and only members:read, events:purchase, rewards:redeem; it can look members up and record sales, but enrolling needs an owner key. mcp is the MCP server URL, or null while agent access is switched off. Missing, unknown or revoked keys get 401.

Step 3 · curl

# Enroll the customer once: 201 when new, 200 when they already exist
curl https://api.rewardfinity.com/v1/members \
  -H "authorization: Bearer $REWARDFINITY_KEY" \
  -H "content-type: application/json" \
  -d '{"email":"customer@example.com","name":"Priya"}'

# Record each sale with your own order identifier
curl https://api.rewardfinity.com/v1/events/purchase \
  -H "authorization: Bearer $REWARDFINITY_KEY" \
  -H "content-type: application/json" \
  -d '{"memberEmail":"customer@example.com","amountCents":4000,"source":"till","externalId":"order-1042"}'

Steps 2 and 3 · JavaScript (fetch)

const API = 'https://api.rewardfinity.com/v1';
const headers = {
  authorization: `Bearer ${process.env.REWARDFINITY_KEY}`,
  'content-type': 'application/json',
};
async function call(method, path, body) {
  const response = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
  const result = await response.json();
  if (!response.ok) throw new Error(`Rewardfinity ${response.status}: ${result.error}`);
  return result;
}

const me = await call('GET', '/me'); // step 2: which business, which role
console.log(me.merchant.name, me.key.role, me.permissions);

await call('POST', '/members', { email: 'customer@example.com', name: 'Priya' }); // once per customer
const sale = await call('POST', '/events/purchase', {
  memberEmail: 'customer@example.com', amountCents: 4000, source: 'till', externalId: 'order-1042',
});
console.log(sale); // { idempotent: false, pointsEarned: 40, referral: null, balance: 40 }

Steps 2 and 3 · Python (requests)

import os
import requests

API = "https://api.rewardfinity.com/v1"
session = requests.Session()
session.headers["authorization"] = f"Bearer {os.environ['REWARDFINITY_KEY']}"

def call(method, path, body=None):
    response = session.request(method, API + path, json=body, timeout=10)
    response.raise_for_status()
    return response.json()

me = call("GET", "/me")  # step 2: which business, which role
print(me["merchant"]["name"], me["key"]["role"], me["permissions"])

call("POST", "/members", {"email": "customer@example.com", "name": "Priya"})  # once per customer
sale = call("POST", "/events/purchase", {
    "memberEmail": "customer@example.com", "amountCents": 4000, "source": "till", "externalId": "order-1042",
})
print(sale)  # {'idempotent': False, 'pointsEarned': 40, 'referral': None, 'balance': 40}

The response carries the new balance. pointsEarned: 40 assumes an illustrative earn rate of 1 point per $1, which you configure. Send the same externalId again and the response says idempotent: true with the balance unchanged.

What arrives immediately, and what does not

Purchase and redemption responses acknowledge ledger changes. Reward creation instead returns a catalog record, not a member balance or externally issued value. Rewardfinity never holds reward funds; catalog creation and recorded redemption do not prove delivery by an external system.

Reward type
What the call does
Response boundary
Points
Ledger entry written; new balance in the response
Same request
Stamps
Accrued by the purchase event — milestone evaluated, remainder carried
Same request
Discount reward
Merchant catalog item created; no member issuance
Catalog record returned
Store credit
Merchant catalog item created; no external credit issued
Catalog record returned
Redemption
Race-safe balance check, then an atomic guarded write
Same request
Gift card, from your vault
Code allocated, spent and issued in one write
Same request
Gift card, by provider payout
Refused with 409 giftcard_payouts_unavailable until a connected provider can pay it
Not today — no payout provider is enabled

If you are looking for a payout rail that buys and mails a gift card on your behalf, this is not one, and there is no setting that turns it into one. Rewardfinity records the entitlement; the merchant funds the value. The vault path works because the codes are already yours — see how the gift-card vault works before designing around it.

Authentication

Create API keys in Dashboard → Settings → Team & access (owner keys have full access; staff keys are scanner-only). Keys are stored only as SHA-256 hashes, so a key is shown once and cannot be recovered — see Security. Send the key as a bearer token:

curl https://api.rewardfinity.com/v1/members \
  -H "authorization: Bearer lp_sk_…" \
  -H "content-type: application/json" \
  -d '{"email":"customer@example.com","name":"Priya"}'

Errors are JSON with an error code: 401 unauthorized for a missing, unknown or revoked key (a paired staff device gets credential_revoked), 403 forbidden with the permission the key’s role lacks, 403 module_not_enabled with the module your plan does not include, and 400 validation with the schema issues.

Endpoints

GET
/v1/me
The business, this key’s role and label, its permissions, and the docs and MCP URLs. Owner and staff keys.
POST
/v1/members
Enroll a member (idempotent by email). Optional referralCode.
GET
/v1/members/:id
Member with balance, lifetime points, tier and stamps.
POST
/v1/events/purchase
Record a purchase → points/stamps/referrals. Idempotent by (source, externalId). Offers & tier multipliers apply automatically.
POST
/v1/events/refund
Refund → clawback at the purchase-time earn rate. Idempotent.
POST
/v1/rewards
Create a reward (discount / store_credit / giftcard).
POST
/v1/redemptions
Redeem for a member — race-safe balance check, idempotencyKey supported.
POST
/v1/campaigns
Create a QR reward campaign (budget + per-person caps). Refused with 409 campaign_payouts_unavailable while campaign payouts are coming soon.
POST
/v1/campaigns/:id/codes
Generate single-use claim codes (≤1000).
PATCH
/v1/campaigns/:id
Kill switch — pause/resume/end.
GET
/v1/campaigns/:id
Campaign stats: codes, claims, spend vs budget.
POST
/v1/promos
Create promo codes; POST /v1/promos/redeem to apply.
POST
/v1/wheels/:id/spin
Spin-to-win (atomic daily caps).
POST
/v1/members/import
CSV member import — idempotent per importId, balances preserved.
GET
/v1/insights
Rule-based program suggestions.
GET
/v1/automations
Configured lifecycle automations: birthday, winback, welcome, anniversary, and expiry.

Public (no auth): GET /p/:slug member page · POST /p/:slug/join self-enroll · POST /p/:slug/redeem member self-redemption · GET /c/:code claim page. Money is integer cents; points are integers; the ledger is append-only.

Why the same purchase twice is safe

A till on a bad connection retries. That is the normal case, not the edge case, and it is why /v1/events/purchase is keyed on (source, externalId) rather than on arrival.

Work it through with a rate of 1 point per $1 — that is an illustrative setting, not a default, because the earn rate is yours to configure. A $40 sale posts once and the member's balance moves by 40. The till times out and posts the same order identifier twice more. The ledger still holds one entry for that order and the balance is still 40, not 120. Refund the order and /v1/events/refund claws back at the rate that applied when the purchase was recorded, so a rate change between sale and refund cannot leave the member up or down.

The same key protects redemptions: idempotencyKey on /v1/redemptions means a retried redeem spends the balance once. Balances come from an append-only integer ledger, so a correction is a new entry rather than an edit to an old one.

  • POST /v1/events/purchase is keyed on (source, externalId); a replay returns idempotent: true and awards nothing twice.
  • POST /v1/events/refund is keyed on (source, refundExternalId).
  • POST /v1/redemptions is keyed on idempotencyKey; a retried redeem spends the balance once.
  • POST /v1/members is keyed on the normalised email; 201 when new, 200 when the member already exists.
  • POST /v1/members/import is keyed on importId.

Webhooks

Set a webhook URL in Dashboard → Settings → Developer, where the signing secret is created too. Rewardfinity POSTs batched events — member.joined, points.earned, stamp.added, reward.redeemed, campaign.claimed, plus automation firings — signed with your secret:

// verify: hex(HMAC-SHA256(rawBody, secret)) === headers['x-torna-signature']
{ "type": "torna.outbox", "events": [
  { "id": "out_…", "kind": "points.earned",
    "payload": { "email": "…", "pointsEarned": 25, "balance": 125 },
    "createdAt": 1783380000000 } ] }

Automation kinds are lifecycle triggers (birthday, winback, welcome, anniversary, and expiry). Firings keep web push and signed webhook events, and queue each consented email, SMS, or WhatsApp leg through the first-party dispatcher. Provider submission occurs only when the corresponding operator-enabled provider path is configured. Failed webhook deliveries stay pending and are retried on the next flush rather than dropped; provider callbacks distinguish acceptance from delivery for direct channels.

POS connections

Square connects natively (Dashboard → Integrations: paste your own access token; Rewardfinity registers its webhook and matches payments to members by email). Shopify stores connect by installing the Rewardfinity app from the Shopify App Store; the app runs as its own workspace inside Shopify admin, with its own members and rewards, and is not linked to a web dashboard account. It uses signed store order and refund webhooks; Shopify POS remains an order-ingestion boundary until a POS extension is separately shipped and verified. Other POS systems (Clover, Lightspeed, Loyverse, Zettle…) drive the signed inbound endpoint POST /webhooks/in/:merchantId — directly or through Zapier/Make. Do not read the Shopify, Square, or generic webhook paths as one shared POS integration.

A fuller map of what connects and what does not is on integrations; the Shopify app is live and approved. What each plan includes is on pricing.

Use Rewardfinity from AI agents (MCP)

Rewardfinity runs a remote MCP server, so an AI app that supports remote MCP connectors can read your workspace and prepare drafts for you to review. Add this URL as a connector:

https://api.rewardfinity.com/mcp

Sign in with OAuth. Your app opens Rewardfinity, you sign in as the owner and choose what it may do (read only, read and draft for your approval, or read and draft automatically) and for how long, up to 90 days. Apps without OAuth can send a personal token as Authorization: Bearer <token>; create one in Settings → AI & API, where it is shown once. Approving either needs an owner sign-in with a verified email, Google, Apple or Microsoft login.

Command-line clients

# Claude Code: add the server, then run /mcp to sign in
claude mcp add --transport http rewardfinity https://api.rewardfinity.com/mcp

# Codex
codex mcp add rewardfinity --url https://api.rewardfinity.com/mcp
codex mcp login rewardfinity

Editors: Add to Cursor · Add to VS Code. Each link asks your editor to add the server; follow its prompts to sign in.

Remote MCP connectors are offered by apps such as Claude, ChatGPT (developer mode), Cursor, VS Code and Codex. Rewardfinity has not yet confirmed any of these apps end to end against its MCP server, and availability depends on the app and your plan, so follow your app’s own connector instructions. Step-by-step setup for each app is in Settings → AI & API.

What a connected agent can do

  • Read the connected workspace, its programs, aggregate insights, setup readiness and saved campaign drafts.
  • Prepare private drafts of programs, messages, card designs, paused campaigns and campaign calendars for the owner to review.

What it cannot do

  • Send messages, publish or activate a program or campaign, charge anyone or change billing.
  • Change points, rewards or balances, record purchases or redeem rewards.
  • See customer contact details or member-level records; insights are aggregate.
  • Switch businesses: each grant covers one workspace, chosen at sign-in, and expires within 90 days.

Writes take two steps. rewardfinity_prepare_draft returns a preview and, unless you chose automatic drafting, a review link the owner opens to approve; rewardfinity_commit_draft then saves it as a private draft or paused campaign. Start with rewardfinity_whoami to confirm which workspace the connection belongs to.

Tokens are stored only as SHA-256 hashes, each completed tool call is recorded in an append-only audit, and you can revoke a connection at any time in Settings → AI & API. Prefer MCP over handing an agent an owner API key: MCP access is scoped and expires; an owner key is neither.

Claude API

Building your own agent? The Claude API’s MCP connector calls Rewardfinity’s tools from Anthropic’s side, using a personal token from Settings → AI & API as its authorization token. Model usage is billed to your own Anthropic account.

TypeScript (@anthropic-ai/sdk)

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic(); // your own Anthropic API key
const response = await client.beta.messages.create({
  model: 'claude-opus-5-5',
  max_tokens: 16000,
  betas: ['mcp-client-2025-11-20'],
  mcp_servers: [{
    type: 'url',
    url: 'https://api.rewardfinity.com/mcp',
    name: 'rewardfinity',
    authorization_token: process.env.REWARDFINITY_TOKEN, // personal token from Settings → AI & API
  }],
  tools: [{ type: 'mcp_toolset', mcp_server_name: 'rewardfinity' }],
  messages: [{ role: 'user', content: 'Which loyalty programs do I have, and what should I review next?' }],
});
for (const block of response.content) if (block.type === 'text') console.log(block.text);

Python (anthropic)

import os
import anthropic

client = anthropic.Anthropic()  # your own Anthropic API key
response = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=16000,
    betas=["mcp-client-2025-11-20"],
    mcp_servers=[{
        "type": "url",
        "url": "https://api.rewardfinity.com/mcp",
        "name": "rewardfinity",
        "authorization_token": os.environ["REWARDFINITY_TOKEN"],  # personal token from Settings → AI & API
    }],
    tools=[{"type": "mcp_toolset", "mcp_server_name": "rewardfinity"}],
    messages=[{"role": "user", "content": "Which loyalty programs do I have, and what should I review next?"}],
)
for block in response.content:
    if block.type == "text":
        print(block.text)

The MCP connector is a beta Claude API feature; check Anthropic’s MCP connector documentation for current availability. The token keeps the same limits as any connected agent: one workspace, reads and reviewable drafts only.

Questions developers ask

Does Rewardfinity have an API for instant reward delivery?
Purchase events synchronously record points and stamp progress and return the member’s balance. POST /v1/rewards creates a merchant-level catalog item, not a member-specific discount or store-credit issuance, and does not return a balance. POST /v1/redemptions separately records an eligible member redemption and the resulting points balance. Neither catalog creation nor recorded redemption proves external discount or store-credit delivery. Gift-card provider payout remains unavailable; Rewardfinity does not hold reward funds.
How long does a reward take to reach the member?
Successful purchase and redemption writes are readable after the request completes. An already-open member page may need a refresh to display current progress or eligibility. Redemption checks the current server-side balance rather than trusting the displayed value. Creating a discount or store-credit catalog item does not itself issue value to a member.
Can the API send a gift card automatically?
Only from your own vault. Import gift-card codes you bought into the encrypted vault, and a redemption allocates, spends and issues one in the same write, with the member revealing it on their member page. The other reading of the question — Rewardfinity buying and sending a card for you — does not happen. That payout is recorded as pending and no provider is enabled, because Rewardfinity does not hold reward funds.
What happens if my POS sends the same purchase twice?
Nothing twice. Purchases are idempotent on the pair (source, externalId), so a retry after a timeout is recognised as the same order and does not stamp or award again. Redemptions take an idempotencyKey for the same reason. This matters more than it sounds: a till retrying on a weak connection is the ordinary case in a shop.
How do I verify a Rewardfinity webhook signature?
Compute hex(HMAC-SHA256(rawBody, secret)) with the secret from your dashboard and compare it to the x-torna-signature header. Use the raw request body, not a re-serialised object, or the bytes will differ and the comparison will fail. Failed deliveries stay pending and are retried on the next flush rather than dropped, so a brief outage on your side does not lose events. Note the asymmetry: the write is synchronous, the outbound webhook is queued, so do not treat your webhook as the confirmation that the write happened.
Do I need an SDK to use the Rewardfinity API?
No. It is plain JSON over HTTPS with a bearer key, so curl or any HTTP client is enough, and there is no client library to keep in step with the service. Money is expressed in integer cents and points as integers, which avoids the rounding drift that floating-point currency introduces in a ledger.