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 many free weeks to grant (respecting the cap).
AppMate owns the attribution + graph + caps; it never grants your app's entitlement. The app maps “weeks owed” to its own premium grant (StoreKit, RevenueCat promotional entitlement, or a local free-access store).

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": {
    "referrerWeeks": 1,
    "refereeEnabled": true,
    "refereeWeeks": 1,
    "referrerLabel": "1 free week for every friend who joins"
  },
  "maxRewardsPerReferrer": 10
}

maxRewardsPerReferrer is a lifetime cap per user (0 or omitted = unlimited — flagged as a farming risk). The share link is appended to messageTemplate automatically.

Wire it in the app (iOS)

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 {
    grantFreeWeeks(reward.weeks)   // your entitlement grant
}

// 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 {
    grantFreeWeeks(reward.weeks)
}

3. Referrer, on every launch

swift
let earned = await RetentionFlow.claimReferralRewards(userId: user.id)
if earned.weeks > 0 { grantFreeWeeks(earned.weeks) }
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.

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:",
  "referrerWeeks": 1,
  "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,
  "refereeReward": { "weeks": 1, "label": null }   // null if only the referrer is rewarded
}
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.