Referral program

A share-with-a-friend growth loop with real install attribution — not a client-side honour-system bonus. Each user gets a unique link and a short, human-readable code (e.g. K7Q4-R9XP); a friend who taps the link or types the code and installs earns a reward for both sides, tracked server-side and capped so it can't be farmed.

How attribution works

  • The app asks AppMate for the user's referral link (appmate.cloud/r/{code}) and shares it.
  • A friend taps the link → the invite landing records a pending referral and stashes a one-time claim token on the clipboard, then sends them to the App Store.
  • On the friend's first launch, the SDK reads the token and calls /attribute— this is the reward trigger. Both sides become eligible; the new user's reward is granted right then.
  • Or, typed code:the friend enters the referrer's short code in an “Enter invite code” field. The SDK calls /attribute with the code instead of a clipboard token — no landing visit, no paste banner — and the referral is attributed on the spot.
  • Installed-app fast path: if the friend already has the app, the invite page opens it via your URL scheme with the code in the link (yourscheme://retention-flow/action?type=referral&code=…), and falls back to the App Store or your app's websiteif it isn't installed (set per flow; the website comes from the app's settings). The app redeems it with RetentionFlow.redeemReferralFromURL(url) from .onOpenURL— instant attribution, no prompt. Register your scheme in the app's settings.
  • The referrer isn't present when the friend installs, so their app claims owed rewards on its next launch via /rewards, which returns how much to grant — an amountof the program's unit (respecting the cap).
AppMate owns the attribution + graph + caps; it never grants your app's entitlement. The app maps the “amount owed” to its own grant — premium time for unit: "week" (StoreKit, RevenueCat promotional entitlement, or a local free-access store), or an in-app currency credit for a custom unit.

Configure the program

Author it in the dashboard's JSON editor or via the MCP update_referral_draft tool:

json
{
  "type": "referral",
  "landing": {
    "eyebrow": "You've been invited",
    "title": "A friend gave you a free week",
    "subtitle": "Install the app and open it — your free week unlocks automatically.",
    "ctaLabel": "Get the app",
    "appStoreUrl": "https://apps.apple.com/app/id000000000"
  },
  "share": {
    "messageTemplate": "I've been loving this app — here's a free week on me:"
  },
  "rewards": {
    "referrer": { "amount": 1, "unit": "week" },
    "refereeEnabled": true,
    "referee": { "amount": 1, "unit": "week" },
    "referrerLabel": "1 free week for every friend who joins"
  },
  "maxRewardsPerReferrer": 10,
  "maxInvitesPerReferrer": 50
}

Each side's reward is a generic { amount, unit }. unit: "week" means free weeks (1–8), which the app maps to trial time. Any other unit is your own in-app currency or credit — e.g. { "amount": 100, "unit": "drop", "unitPlural": "drops" } — which the app grants by reading amount + unit. Legacy { referrerWeeks, refereeWeeks } configs are still accepted and auto-upgraded to unit: "week".

maxRewardsPerReferrer is a lifetime cap on rewards earned per user (0 or omitted = unlimited — flagged as a farming risk). maxInvitesPerReferrercaps how many people one referrer can invite (0 or omitted = unlimited); once reached, new friends can still install but aren't credited to that referrer. The share link is appended to messageTemplate automatically.

Wire it in the app (iOS / macOS)

1. Share

swift
// When the user taps "Invite a friend":
if let url = await RetentionFlow.referralShareLink(userId: user.id) {
    presentShareSheet(text: shareMessage, url: url)
}

// Optional: a copyable "Your code" chip so people can share the code directly:
if let code = await RetentionFlow.referralShareCode(userId: user.id) {
    yourCodeLabel.text = code   // e.g. "K7Q4-R9XP"
}
// Do NOT grant the reward here — it's earned only when a friend installs.

2. New user, on first launch

swift
// Deferred clipboard handoff (shows the paste banner):
if let r = await RetentionFlow.attributeReferral(userId: user.id),
   let reward = r.refereeReward {
    // unit is "week" (free weeks) or your custom currency ("drop", …)
    grantReward(amount: reward.amount, unit: reward.unit)
}

// Or, from an "Enter invite code" field (no clipboard, no paste banner):
if let r = await RetentionFlow.redeemReferral(code: enteredCode, userId: user.id),
   let reward = r.refereeReward {
    grantReward(amount: reward.amount, unit: reward.unit)
}

// For a free-weeks program you can still use reward.weeks (0 for custom units).

3. Referrer, on every launch

swift
let earned = await RetentionFlow.claimReferralRewards(userId: user.id)
if earned.amount > 0 { grantReward(amount: earned.amount, unit: earned.unit) }
Like onboarding, the deferred handoff uses the clipboard, so attributeReferralshows the iOS paste banner — call it at a natural “setting things up” moment on first launch. On macOS the share/redeem/claim calls (referralShareLink, referralShareCode, redeemReferral(code:), redeemReferralFromURL, claimReferralRewards) work the same; only the clipboard attributeReferral handoff is iOS-only — on macOS attribute via the typed code or a deep link instead.

Public endpoints (raw HTTP)

The SDK calls these for you — but here are the literal contracts if you're integrating without it (or from another platform). Both are unauthenticated POSTs scoped by appSlug.

Mint / fetch a user's code — POST /api/public/referral/code

json
// request
{
  "appSlug": "your-app-slug",
  "userId": "your-end-user-id"
}

// response 200
{
  "code": "K7Q4R9XP",            // raw code
  "displayCode": "K7Q4-R9XP",    // formatted — use this in a "Your code" chip
  "shareUrl": "https://appmate.cloud/r/K7Q4R9XP",
  "shareMessage": "I've been loving this — a free week on me:",
  "referrerReward": { "amount": 1, "unit": "week", "unitPlural": null, "label": null, "weeks": 1 },
  "referrerWeeks": 1,             // legacy; 0 for custom-unit programs
  "referrerLabel": null
}

Redeem an invite — POST /api/public/referral/attribute

Identify the invite with exactly one of code (a typed short code) or claimToken (the amrf_… token from the clipboard handoff).

json
// request — typed-code path
{
  "appSlug": "your-app-slug",
  "code": "K7Q4-R9XP",          // case-insensitive; dashes/spaces ignored
  "userId": "referee-user-id"    // or "anonymousId" — optional but recommended
}

// response 200
{
  "attributed": true,
  "alreadyAttributed": false,
  // null if only the referrer is rewarded. weeks is 0 for custom-unit programs.
  "refereeReward": { "amount": 1, "unit": "week", "unitPlural": null, "label": null, "weeks": 1 }
}
The redeem field is literally code — not referralCode or shortCode. Errors: 404 unknown/invalid code; 409 with { "selfReferral": true } if the redeemer owns the code. Redeeming is idempotent per referee.

Where the data lands

  • Dashboard → your app → Referral program installs attributed — a Code issuers summary (who minted a code and how many installs/pending it drove, including how many came from a typed code), the full invite graph with a Via (link / typed code) marker, status + reward flags, and CSV export.
  • Programmatically: GET /api/v1/apps/{appId}/referrals or the MCP list_referrals / export_referrals_csv tools.

See the API reference for the public endpoints and the iOS SDK for the full method signatures.