RekomiRekomiBlogPricing
Rekomi Docs
Rekomi Docs
Welcome to Rekomi
API overviewAuthenticationOAuth 2.0Server-to-server trackingTracking script & window.RekomiTrack leads and signupsNo-code & non-Stripe checkoutsCustom domainConversion currencyCoupon code trackingSub-affiliate recruiting APIWebhooksZapierWhite-label embedMCP serverAPI reference
For developers
|Developers|

Coupon code tracking

Create per-affiliate Stripe promotion codes, attribute conversions on redemption, and clawback refunds automatically.

Rekomi mints Stripe coupons + promotion codes on your behalf and credits the right affiliate when a customer redeems one at Stripe Checkout. No click cookie required, no tracking script. Use this when your buyers will type a code rather than click a link (influencer programs, podcast spots, offline ads).

Plan tier required: Growth or higher for direct rk_live_* API access. JWT (dashboard) calls work on every paid plan.

Concepts

  • Campaign coupon: the discount semantics. Maps 1:1 to a Stripe coupon. You set the percent or amount, duration, max redemptions, expiry. Once created, the discount is immutable (Stripe rule). To change a discount, archive the parent and create a new one.
  • Affiliate coupon: a redeemable code attached to one affiliate. Maps 1:1 to a Stripe promotion_code. Multiple per affiliate allowed (vanity variants like SARAH20, SARAHSUMMER).

Attribution: when a customer redeems a promotion code at checkout, Rekomi's Stripe webhook sees invoice.discounts[].promotion_code and credits the affiliate who owns it. If OverridesClickAttribution is on (default), the coupon wins even when there's a click cookie. Refunds via charge.refunded decrement the redemption count and mark the conversion refunded.

Authentication

All endpoints accept either a dashboard JWT or a public-API bearer:

Authorization: Bearer rk_live_xxx

Get a key in /dashboard/settings/api. Reads require read scope; writes require read_write.

Create a campaign coupon

POST /api/v1/campaigns/{programId}/coupons

Request body:

{
  "name": "Summer 20% off",
  "discountType": "PercentOff",
  "discountValue": 20,
  "discountCurrency": null,
  "duration": "Once",
  "durationInMonths": null,
  "maxRedemptionsPerCode": 100,
  "expiresAt": "2026-12-31T23:59:59Z",
  "allowAffiliateSelfMint": false,
  "overridesClickAttribution": true
}

Validation:

  • discountType is PercentOff (discountValue 1-100) or AmountOff (discountValue positive cents, discountCurrency required ISO 4217).
  • duration is Once | Repeating | Forever. When Repeating, durationInMonths is required (1-94).
  • expiresAt must be in the future when set.
  • maxRedemptionsPerCode ≥ 1 when set.

Response: 201 Created with the full campaign coupon record including stripeCouponId.

curl -X POST https://api.rekomi.com/api/v1/campaigns/$PROGRAM_ID/coupons \
  -H "Authorization: Bearer $REKOMI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer 20% off",
    "discountType": "PercentOff",
    "discountValue": 20,
    "duration": "Once",
    "allowAffiliateSelfMint": false,
    "overridesClickAttribution": true
  }'
import Rekomi from "@rekomi/sdk";
const r = new Rekomi(process.env.REKOMI_API_KEY!);
const coupon = await r.coupons.createForCampaign(programId, {
  name: "Summer 20% off",
  discountType: "PercentOff",
  discountValue: 20,
  duration: "Once",
  allowAffiliateSelfMint: false,
  overridesClickAttribution: true,
});

List, update, archive, delete

GET    /api/v1/campaigns/{programId}/coupons
GET    /api/v1/campaigns/{programId}/coupons/{id}
PATCH  /api/v1/campaigns/{programId}/coupons/{id}        # editable: name, toggles only
POST   /api/v1/campaigns/{programId}/coupons/{id}/archive
DELETE /api/v1/campaigns/{programId}/coupons/{id}        # only when no per-affiliate codes exist

PATCH accepts only name, allowAffiliateSelfMint, overridesClickAttribution. Discount semantics are immutable per Stripe's rules. Archive sets isActive=false, deactivates all child promotion codes in Stripe, and marks them revoked locally.

Generate a per-affiliate coupon

POST /api/v1/affiliates/{affiliateId}/coupons

Request body:

{
  "campaignCouponId": "c0upon01-...",
  "code": "SARAH20"
}
  • campaignCouponId is required. The campaign coupon must belong to the same program as the affiliate.
  • code is optional. When null, Rekomi generates an 8-character Crockford-style code (no 0/O, 1/I/L for transcribability). When set, must match ^[A-Z0-9_-]{3,32}$.
  • Codes are unique per organization. Duplicates return 422 code_already_taken.

Response: 201 Created with the new affiliate coupon record including stripePromotionCodeId.

List + revoke

GET    /api/v1/affiliates/{affiliateId}/coupons
DELETE /api/v1/affiliates/{affiliateId}/coupons/{id}    # revokes (deactivates in Stripe + marks inactive)

Affiliate self-mint

When the brand sets allowAffiliateSelfMint=true on a campaign coupon, the affiliate can mint their own vanity codes from /a:

POST /api/me/coupons/mint

Request body:

{
  "campaignCouponId": "c0upon01-...",
  "code": "SARAH-SPRING"
}

The endpoint runs under affiliate-side auth (Clerk JWT, no org context). The caller must belong to the same program the campaign coupon targets, be Approved status, and the vanity code must satisfy the regex above.

Attribution behavior

On every Stripe invoice.paid or customer.subscription.created event, Rekomi extracts discounts[].promotion_code from the payload. If the promotion code maps to one of your AffiliateCoupon rows:

  1. overridesClickAttribution=true (default): attribute to the coupon's owning affiliate even when a click cookie exists. Conversion.attributionMethod = "coupon".
  2. overridesClickAttribution=false: check metadata + customer history first. If those don't credit anyone, fall back to the coupon owner. attributionMethod = "metadata_or_customer" or "coupon" depending on which won.
  3. Both click + coupon point to the same affiliate: attributionMethod = "coupon_with_metadata".

The conversion row carries affiliateCouponId pointing at the redeemed coupon. Refund cascade uses this to decrement redemptionCount and lastRedeemedAt is also bumped on every successful conversion.

Free / 100%-off coupon signups become leads

When a referred visitor redeems a fully discounting (100%-off) coupon, the subscription is created but no paid invoice ever fires. Rekomi records that signup as a lead (a LeadReward lead-only row, no commission), credited to the affiliate who owns the code, so it appears in your funnel even if the customer never pays. The coupon's redemption count still increments, and if the customer later starts paying, that sale is matched back to the lead and commissioned. See Track leads and signups.

Refunds

When Stripe fires charge.refunded, Rekomi:

  1. Finds the conversion by payment_intent_id or charge_id.
  2. Inserts a negative SubscriptionEvent (reverses commission).
  3. Sets Conversion.status = Refunded and refundedAt.
  4. If affiliateCouponId is set, decrements the coupon's redemptionCount (guarded against negative; partial-refund replays don't drive the counter below zero).
  5. Emits conversion.refunded webhook.

Webhook events

See the Webhooks reference. Four events live in the affiliate_coupon.* family: created, updated, deactivated, deleted. Subscribe to all of them with affiliate_coupon.* or pick specific events at endpoint creation.

Rate limits

The campaign coupon and per-affiliate coupon endpoints share the standard authenticated rate-limit bucket (600 req/min per key; see Rate limits). Stripe-side rate limits apply independently; bulk-minting more than a few codes per second can return 429, Rekomi retries idempotently using a Rekomi-side stable id, so a retry from your side is safe.

Conversion currency

ISO 4217 allowlist, error codes, Stripe normalization, and the home-currency settings endpoint.

Sub-affiliate recruiting API

Endpoints, override conversion shape, webhook payloads, and apply-form semantics for the bounded 1-tier sub-affiliate model.

On this page

ConceptsAuthenticationCreate a campaign couponList, update, archive, deleteGenerate a per-affiliate couponList + revokeAffiliate self-mintAttribution behaviorRefundsWebhook eventsRate limits