pex

Programs + memberships

A program is a merchant's commission offer. A membership is one partner on one program. Programs are how you express "I'll pay $5 per install and 20% on every subscription renewal." Memberships are how a specific partner's rate and history live alongside the program's default.

Program shape

FieldMeaning
namePublic title, shown in marketplace + partner portal
descriptionMarketplace card body, ≤ 600 characters
verticalsaas / ecommerce / fintech / marketplace / hardware / enterprise / creator_tools — drives marketplace filters
statusactive / paused / archived — paused + archived reject new memberships
visibilitypublic (listed in marketplace) or private (invite-only)
approvalModeauto (instant join from marketplace) or manual (merchant reviews each application)
commissionRulesArray of CommissionRule objects — each scoped to an event type (install, purchase, subscription_renewal) with its own rate, caps, and optional time bounds. See Commission rules.
commissionWindowHow long after first click a conversion still counts (days)
endsAtOptional ISO timestamp. When the date passes, the program stops accepting new memberships and no new conversions are attributed. Existing approved commissions still pay out.
couponSyncModestripe / webhook / manual — how coupon codes are synced to Stripe Promotion Codes and attributed back to partners. See Coupon tracking.
autoPayoutPolicymanual / auto / auto_under_cap — when transfers fire
autoPayoutCapUsdCeiling for auto_under_cap (per payout, not per day)
approvalRate, approvalMedianSeconds, paymentOnTimeRateReputation counters (nightly cron)

Commission rules

Programs now carry an array of commissionRules instead of a single commissionStructure. Each rule targets one event type and has its own rate:

commissionRules: [
  {
    event: "install",
    type: "cpa",
    amountUsd: 5,
  },
  {
    event: "purchase",
    type: "revshare",
    percentage: 20,
  },
  {
    event: "subscription_renewal",
    type: "revshare",
    percentage: 15,
    maxCredits: 12, // stop after 12 renewals
  },
]

Time-bounded promotional bonuses use effectiveFrom / effectiveTo:

{
  event: "purchase",
  type: "cpa",
  amountUsd: 10,
  effectiveFrom: "2026-06-01T00:00:00Z",
  effectiveTo: "2026-06-30T23:59:59Z",
}

The rule only applies to conversions that occur within the window. Outside the window, the partner earns the base rule for that event. See Commission rules for the full type reference.

Program expiry

Set endsAt to auto-sunset a program:

  • Before endsAt: program operates normally.
  • After endsAt: no new memberships accepted; new conversion events are ignored; existing approved commissions continue through the payout pipeline.
  • endsAt is mutable — extend or remove it at any time while the program is still active.

Membership shape

Each AffiliateMembership lives at (projectKey, profileId) and carries:

  • programId — which program this membership is bound to
  • commissionHistory — append-only list of CommissionChangeEntry records, each with {commissionRules, effectiveFrom, reason, source}
  • statusactive / paused / terminated
  • Performance counters: totalClicks, approvedConversions, totalEarnedUsd, totalPaidUsd

The source field in commission history tracks why the rate is what it is:

SourceMeaning
program_defaultComes from the program's current default commission rules
invite_overrideSet at invite time (sticky — survives default updates)

Commission rates are always inherited from the program. Per-membership overrides are set only at invite time via the InviteCreatorDialog flow.

Commission inheritance model

When you create new program defaults and call applyProgramDefaultToMembers(programId), only memberships where the current source is program_default get updated. Sticky invite overrides are preserved.

This gives you two knobs:

  • Bulk rate change — update the program, fan out to everyone on the default rate
  • Individual negotiation — set a custom rate at invite time for a specific partner

Lifecycle

  1. Merchant creates a program: createProgram({...})
  2. Partners join via marketplace (public+auto), invite (any), or application (public+manual)
  3. Conversions arrive → commission rules evaluate per event type → AffiliateConversion records accumulate
  4. Merchant approves payouts in batches → HoldbackEntry records created
  5. Release cron fires past-due entries → available balance accumulates
  6. Payout orchestrator fires transfers per merchant's autoPayoutPolicy cycle

Marketplace discovery

Public programs show up in partners.apex.inc/programs ranked by:

  1. Merchant reputation (program approvalRate, paymentOnTimeRate)
  2. Commission attractiveness (amount or percentage)
  3. Volume (recent conversions)
  4. Freshness (newer programs get a small boost for discovery)

Private programs never appear; partners must be invited.