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|

Sub-affiliate recruiting API

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

This is the developer-side surface for sub-affiliate recruiting: the brand-side concept page covers the "what" and "why"; this page covers the wire shapes.

There are five endpoints, one new conversion-row shape, one webhook variation, and one apply-form field. Everything is bounded to a single tier of recruitment depth; there is no chain propagation.

Endpoints

All five are mounted under /api/v1/* with the standard Authorization: Bearer rk_live_* API key auth, plus the same JWT path for the in-product dashboard.

GET /api/v1/programs/{id}/sub-affiliate-config

Scope: read. Returns the current sub-affiliate config for a campaign.

curl -sS https://api.rekomi.com/api/v1/programs/0789abcd-.../sub-affiliate-config \
  -H "Authorization: Bearer $REKOMI_API_KEY"

Response:

{
  "enabled": true,
  "percent": 10.0
}
  • enabled: whether the recruiting feature is on for this campaign.
  • percent: override percent on parent commission. Range 0 to 100. When enabled is false, this field is still returned (it just doesn't fire on new conversions).

PUT /api/v1/programs/{id}/sub-affiliate-config

Scope: read_write. Role: Owner (on JWT). Plan: Pro+. Email verification required.

Audit-logged as program.sub_affiliate_config_changed.

curl -sS -X PUT https://api.rekomi.com/api/v1/programs/0789abcd-.../sub-affiliate-config \
  -H "Authorization: Bearer $REKOMI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "percent": 10
  }'

Response (200):

{
  "enabled": true,
  "percent": 10.0
}

Failures:

HTTPerrorCause
400validation_failedPercent outside [0, 100] or not a number
402plan_tier_requiredOrg below Pro and not trialing
403email_not_verifiedOwner exists but has not verified email
403(empty)Caller is not Owner
404(empty)Program not in your org

GET /api/v1/me/sub-affiliate/link?programId={id}

Scope: read. Affiliate-side. Returns the calling affiliate's personal recruitment link for a campaign. Auth is the affiliate's /a JWT, not a brand API key.

curl -sS "https://api.rekomi.com/api/v1/me/sub-affiliate/link?programId=0789abcd-..." \
  -H "Authorization: Bearer $AFFILIATE_JWT"

Response:

{
  "affiliateId": "01234567-...",
  "programId": "0789abcd-...",
  "programSlug": "default",
  "url": "https://your-brand.example.com/p/default?recruited_by=01234567-..."
}

Returns 400 sub_affiliate_recruiting_disabled if the campaign has enabled = false, and 404 if the campaign isn't visible to the calling affiliate.

GET /api/v1/me/sub-affiliate/recruits

Scope: read. Affiliate-side. Lists the calling affiliate's downline. Cursor-paginated.

curl -sS "https://api.rekomi.com/api/v1/me/sub-affiliate/recruits?take=50" \
  -H "Authorization: Bearer $AFFILIATE_JWT"

Response:

{
  "items": [
    {
      "affiliateId": "fed00b00-...",
      "email": "recruit@example.com",
      "fullName": "Recruited Person",
      "status": "Approved",
      "totalCommissionGeneratedCents": 12400,
      "overrideEarnedCents": 1240,
      "currency": "USD",
      "joinedAt": "2026-05-12T10:00:00Z"
    }
  ],
  "nextCursor": null
}
  • totalCommissionGeneratedCents: this recruit's own commission income (excludes override rows so it never double-counts when the recruit later recruits someone else).
  • overrideEarnedCents: total override the calling affiliate has earned from this recruit, in currency.
  • Only Approved recruits accrue override income. Pending/Rejected recruits show in the list with overrideEarnedCents = 0.

GET /api/v1/affiliates/{id}/sub-affiliates

Scope: read. Brand-side drilldown: lets a brand operator inspect any affiliate's downline. Returns a flat array (no pagination wrapper) with slightly different field names than the affiliate-side endpoint.

curl -sS "https://api.rekomi.com/api/v1/affiliates/01234567-.../sub-affiliates" \
  -H "Authorization: Bearer $REKOMI_API_KEY"
[
  {
    "id": "fed00b00-...",
    "email": "recruit@example.com",
    "fullName": "Recruited Person",
    "status": "Approved",
    "totalCommissionCents": 12400,
    "overrideEarnedCents": 1240,
    "currency": "USD"
  }
]

Override conversion row

When affiliate A recruits affiliate B, and B closes a sale, two Conversion rows are written in the same transaction:

  1. Parent commission row for B. Looks like a normal conversion. AmountCents is the sale, CommissionCents is B's commission, AttributionMethod is whatever path attributed the sale (click, coupon, pixel, s2s, etc.). ParentAffiliateConversionId is null.
  2. Override row for A. AmountCents = 0, CommissionCents = (B.commission × Program.SubAffiliateCommissionPercent / 100), rounded down to the nearest minor unit. AttributionMethod = "sub_affiliate_override". ParentAffiliateConversionId points at the parent row's id.

Example conversion list query showing both rows:

curl -sS "https://api.rekomi.com/api/v1/conversions?programId=0789abcd-...&take=50" \
  -H "Authorization: Bearer $REKOMI_API_KEY"

Response (relevant fields only):

{
  "items": [
    {
      "id": "c0nv0parent-...",
      "affiliateId": "01234567-B-...",
      "programId": "0789abcd-...",
      "amountCents": 10000,
      "currency": "USD",
      "commissionCents": 2000,
      "attributionMethod": "click",
      "parentAffiliateConversionId": null,
      "status": "Approved"
    },
    {
      "id": "c0nv0override-...",
      "affiliateId": "01234567-A-...",
      "programId": "0789abcd-...",
      "amountCents": 0,
      "currency": "USD",
      "commissionCents": 200,
      "attributionMethod": "sub_affiliate_override",
      "parentAffiliateConversionId": "c0nv0parent-...",
      "status": "Approved"
    }
  ],
  "nextCursor": null,
  "hasMore": false
}

Notes:

  • Override rows always have amountCents = 0. The sale amount lives on the parent row; duplicating it on the override would double-count revenue on dashboards.
  • Override rows always have commissionCents > 0. If the computed override rounds to zero (very small parent commission × very small percent), the row is not created.
  • parentAffiliateConversionId is a foreign key to the same Conversion table. Use it to join override rows back to the sale that generated them.

Refund cascade

When the parent conversion is refunded (partially or fully) via the Stripe webhook path, the pixel-refund endpoint, or the S2S refund endpoint, Rekomi computes the proportional fraction of the parent commission that was clawed back and applies the same fraction to the override.

  • Full refund of the parent: override is fully reversed.
  • Partial refund (e.g., 40% of the sale amount): both the parent commission and the override are reduced by 40%.

Both rows fire conversion.refunded webhook events. Consumers tracking lifetime earnings should subtract the commissionReversedCents from their running totals on both rows.

Webhook payloads

conversion.created (override row)

Same envelope as the normal conversion.created event. The body distinguishes override rows via attributionMethod:

{
  "id": "evt_01HVABCDEFGHIJKLMN",
  "type": "conversion.created",
  "createdAt": "2026-05-12T03:14:25.000Z",
  "organizationId": "01HORG123...",
  "data": {
    "conversionId": "c0nv0override-...",
    "affiliateId": "01234567-A-...",
    "programId": "0789abcd-...",
    "amountCents": 0,
    "currency": "USD",
    "commissionCents": 200,
    "status": "Pending",
    "attributionMethod": "sub_affiliate_override",
    "parentAffiliateConversionId": "c0nv0parent-...",
    "type": "OneTime"
  }
}

conversion.refunded (override row)

Emitted on every cascade iteration where the override's RefundedAmountCents was incremented. refundedAmountCents and commissionReversedCents are the THIS-event clawback; cumulativeRefundedAmountCents is the lifetime total on the row; isFullRefund is true only on the iteration that crossed the override into fully-refunded state. Carries the parent linkage + attribution-method so consumers can route override-row events separately.

{
  "id": "evt_01HVZYXWVU...",
  "type": "conversion.refunded",
  "createdAt": "2026-05-15T11:00:00Z",
  "organizationId": "01HORG123...",
  "data": {
    "conversionId": "c0nv0override-...",
    "affiliateId": "01234567-A-...",
    "programId": "0789abcd-...",
    "refundedAmountCents": 200,
    "cumulativeRefundedAmountCents": 200,
    "commissionReversedCents": 200,
    "currency": "USD",
    "isFullRefund": true,
    "refundedAt": "2026-05-15T11:00:00Z",
    "parentAffiliateConversionId": "c0nv0parent-...",
    "attributionMethod": "sub_affiliate_override"
  }
}

Warning for webhook consumers

If your handler filters conversion.created events by amountCents > 0 to drop "zero-revenue" events, you will silently lose all override events. Override rows are real commission obligations with commissionCents > 0 but always amountCents = 0.

Better filters:

  • Filter on status (e.g., only act on Approved or Paid).
  • Filter on commissionCents > 0 if you want anything that owes the affiliate money.
  • Match on attributionMethod if you want to route override rows to a different downstream pipeline (e.g., a separate accounting bucket for recruiter income vs. direct-sale commission).

Apply form: recruitedByAffiliateId

The public apply endpoint optionally accepts a recruitedByAffiliateId field. When set, the new affiliate's RecruitedByAffiliateId is populated with that value, establishing the 1-tier link.

curl -sS https://api.rekomi.com/api/p/default/apply \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newaffiliate@example.com",
    "fullName": "New Affiliate",
    "recruitedByAffiliateId": "01234567-A-..."
  }'

Rules:

  • The id must reference an Approved affiliate in the same org as the campaign being applied to. Mismatches are silently dropped (the application succeeds, but RecruitedByAffiliateId stays null).
  • Self-recruit attempts (i.e., the applicant's eventual id equaling the supplied id) are blocked at row creation.
  • If the campaign has enabled = false, the field is accepted on the application but the override row will never fire on sales (it's stored for auditability but is operationally inert).
  • The field is not discoverable from the public apply schema for SEO reasons; only callers who have a recruiter id (typically from a tagged URL) should pass it.

In practice you don't construct this by hand. The recruitment URL (returned by GET /api/v1/me/sub-affiliate/link) carries recruited_by={affiliateId} as a query param. The public apply landing page reads that param and forwards it as recruitedByAffiliateId in the form submission.

MCP tools

For AI-agent integrations, three tools wrap the brand-side endpoints:

ToolMaps to
get_program_sub_affiliate_configGET /api/v1/programs/{id}/sub-affiliate-config
set_program_sub_affiliate_configPUT /api/v1/programs/{id}/sub-affiliate-config
list_affiliate_sub_affiliatesGET /api/v1/affiliates/{id}/sub-affiliates

All three accept either rk_live_* (direct API key) or rk_mcp_* (relayed via mcp.rekomi.com). The affiliate-side /me/sub-affiliate/* endpoints are Clerk-JWT-only and intentionally not exposed via MCP (the relay carries brand-org identity, not per-affiliate identity). See MCP for the auth model.

See also

  • Sub-affiliate recruiting (brands): concept, math, refund cascade, FAQ.
  • Conversion currency: override rows carry the same currency field; the override commission is denominated in the parent sale's currency.
  • Webhooks: event envelope, signature verification, retry semantics.
  • API reference: terse endpoint signatures alongside the rest of /api/v1/*.

Coupon code tracking

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

Webhooks

Subscribe to events, verify signatures, and handle retries.

On this page

EndpointsGET /api/v1/programs/{id}/sub-affiliate-configPUT /api/v1/programs/{id}/sub-affiliate-configGET /api/v1/me/sub-affiliate/link?programId={id}GET /api/v1/me/sub-affiliate/recruitsGET /api/v1/affiliates/{id}/sub-affiliatesOverride conversion rowRefund cascadeWebhook payloadsconversion.created (override row)conversion.refunded (override row)Warning for webhook consumersApply form: recruitedByAffiliateIdMCP toolsSee also