# MCP server
Source: https://rekomi.com/docs/developers/mcp

> Connect AI assistants (Claude, Cursor, Continue, Zed, ChatGPT, Windsurf) to your Rekomi account through Model Context Protocol.

---
title: MCP server
description: Connect AI assistants (Claude, Cursor, Continue, Zed, ChatGPT, Windsurf) to your Rekomi account through Model Context Protocol.
---

Rekomi runs a hosted MCP server at `https://mcp.rekomi.com/api/mcp`. Pick your AI client below, paste two values, restart. That's it.

<Cards>
  <Card icon={<img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/claude.svg?v=4" alt="Claude" style={{height: 24, width: 24, objectFit: "contain"}} />} title="Claude Desktop" href="#connect-claude-desktop" description="Paste two lines into claude_desktop_config.json. Restart." />
  <Card icon={<img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/claude.svg?v=4" alt="Claude Code" style={{height: 24, width: 24, objectFit: "contain"}} />} title="Claude Code" href="#connect-claude-code" description="One claude mcp add command. CLI persists the config." />
  <Card icon={<img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/openai.svg?v=4" alt="ChatGPT" style={{height: 24, width: 24, objectFit: "contain"}} />} title="ChatGPT" href="#connect-chatgpt" description="Settings → Connectors → Add custom connector. UI only." />
  <Card icon={<img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/cursor.svg?v=4" alt="Cursor" style={{height: 24, width: 24, objectFit: "contain"}} />} title="Cursor" href="#connect-cursor" description="Add to ~/.cursor/mcp.json. Same JSON shape as Claude." />
  <Card icon={<img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/windsurf.svg?v=4" alt="Windsurf" style={{height: 24, width: 24, objectFit: "contain"}} />} title="Windsurf" href="#connect-windsurf" description="Cascade panel → MCP servers → Add. Streamable-HTTP." />
  <Card icon={<img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/continue.png?v=4" alt="Continue" style={{height: 24, width: 24, objectFit: "contain"}} />} title="Continue" href="#connect-continue" description="Add to ~/.continue/config.json. Restart the extension." />
  <Card icon={<img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/zed.svg?v=4" alt="Zed" style={{height: 24, width: 24, objectFit: "contain"}} />} title="Zed" href="#connect-zed" description="Add to ~/.config/zed/settings.json under context_servers." />
</Cards>

## Generate a connection token

Every connection needs an `rk_mcp_*` token. It's the only credential your AI client uses.

1. Sign in to [app.rekomi.com](https://app.rekomi.com).
2. Open **Settings → AI assistant**.
3. Click **Generate connection token**, give it a name (e.g. *Claude Desktop laptop*), and copy the `rk_mcp_*` value. It is shown exactly once.

Owner-only to create. Up to 10 active per organization. Revoke at any time; the change propagates within ~10 seconds. Available on every plan, including during the trial.

<h2 id="connect-claude-desktop" className="not-prose flex items-center gap-3 mt-12 mb-4 font-display text-3xl font-bold tracking-tight text-[#23272a] scroll-mt-24">
  <img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/claude.svg?v=4" alt="" width="28" height="28" style={{ width: 28, height: 28, objectFit: "contain", flex: "none" }} />
  Connect Claude Desktop
</h2>

Edit your `claude_desktop_config.json`:

```json title="claude_desktop_config.json"
{
  "mcpServers": {
    "rekomi": {
      "url": "https://mcp.rekomi.com/api/mcp",
      "headers": {
        "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE"
      }
    }
  }
}
```

Restart Claude Desktop. Ask it "what's in my Rekomi account?" to confirm.

<h2 id="connect-claude-code" className="not-prose flex items-center gap-3 mt-12 mb-4 font-display text-3xl font-bold tracking-tight text-[#23272a] scroll-mt-24">
  <img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/claude.svg?v=4" alt="" width="28" height="28" style={{ width: 28, height: 28, objectFit: "contain", flex: "none" }} />
  Connect Claude Code
</h2>

```bash
claude mcp add --transport http rekomi https://mcp.rekomi.com/api/mcp \
  --header "Authorization: Bearer rk_mcp_YOUR_TOKEN_HERE"
```

The URL **must** come before `--header`. The `--header` flag is variadic, so any positional argument after it gets eaten as another header. If you see `error: missing required argument 'commandOrUrl'`, that's the cause.

The CLI persists the config to `~/.claude.json` and subsequent sessions auto-connect.

<h2 id="connect-chatgpt" className="not-prose flex items-center gap-3 mt-12 mb-4 font-display text-3xl font-bold tracking-tight text-[#23272a] scroll-mt-24">
  <img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/openai.svg?v=4" alt="" width="28" height="28" style={{ width: 28, height: 28, objectFit: "contain", flex: "none" }} />
  Connect ChatGPT
</h2>

ChatGPT supports MCP via the Connectors UI on chatgpt.com (Plus, Pro, Team, Enterprise, 2026 Apps SDK rollout). There's no JSON config to paste; the setup is in the UI.

1. Open chatgpt.com → profile menu (bottom-left) → **Settings** → **Connectors** → **Add custom connector**.
2. Name: `Rekomi`.
3. MCP server URL: `https://mcp.rekomi.com/api/mcp`
4. Authentication: **Custom header**.
   - Header name: `Authorization`
   - Header value: `Bearer rk_mcp_YOUR_TOKEN_HERE`
5. Save. Start a new chat, enable the Rekomi connector, ask "list my pending Rekomi affiliates".

<h2 id="connect-cursor" className="not-prose flex items-center gap-3 mt-12 mb-4 font-display text-3xl font-bold tracking-tight text-[#23272a] scroll-mt-24">
  <img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/cursor.svg?v=4" alt="" width="28" height="28" style={{ width: 28, height: 28, objectFit: "contain", flex: "none" }} />
  Connect Cursor
</h2>

Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project root:

```json title="~/.cursor/mcp.json"
{
  "mcpServers": {
    "rekomi": {
      "url": "https://mcp.rekomi.com/api/mcp",
      "headers": {
        "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE"
      }
    }
  }
}
```

<h2 id="connect-windsurf" className="not-prose flex items-center gap-3 mt-12 mb-4 font-display text-3xl font-bold tracking-tight text-[#23272a] scroll-mt-24">
  <img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/windsurf.svg?v=4" alt="" width="28" height="28" style={{ width: 28, height: 28, objectFit: "contain", flex: "none" }} />
  Connect Windsurf
</h2>

Open the Cascade panel and click the hammer/plug icon → **MCP servers** → **Add server**. Pick Streamable-HTTP transport and paste:

```json title="~/.codeium/windsurf/mcp_config.json"
{
  "mcpServers": {
    "rekomi": {
      "serverUrl": "https://mcp.rekomi.com/api/mcp",
      "headers": {
        "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE"
      }
    }
  }
}
```

Restart Windsurf. The Rekomi tools appear under **Available tools** in the Cascade panel.

<h2 id="connect-continue" className="not-prose flex items-center gap-3 mt-12 mb-4 font-display text-3xl font-bold tracking-tight text-[#23272a] scroll-mt-24">
  <img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/continue.png?v=4" alt="" width="28" height="28" style={{ width: 28, height: 28, objectFit: "contain", flex: "none" }} />
  Connect Continue
</h2>

Add to `~/.continue/config.json` under `experimental.modelContextProtocolServers`, then restart the Continue extension:

```json title="~/.continue/config.json"
{
  "experimental": {
    "modelContextProtocolServers": [
      {
        "transport": {
          "type": "streamable-http",
          "url": "https://mcp.rekomi.com/api/mcp"
        },
        "headers": {
          "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE"
        }
      }
    ]
  }
}
```

<h2 id="connect-zed" className="not-prose flex items-center gap-3 mt-12 mb-4 font-display text-3xl font-bold tracking-tight text-[#23272a] scroll-mt-24">
  <img src="https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/platforms/zed.svg?v=4" alt="" width="28" height="28" style={{ width: 28, height: 28, objectFit: "contain", flex: "none" }} />
  Connect Zed
</h2>

Add to `~/.config/zed/settings.json` under `context_servers`:

```json title="~/.config/zed/settings.json"
{
  "context_servers": {
    "rekomi": {
      "source": "custom",
      "command": null,
      "settings": {
        "url": "https://mcp.rekomi.com/api/mcp",
        "headers": {
          "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE"
        }
      }
    }
  }
}
```

## How it works

AI assistants speak MCP to read your affiliate program, approve applications, draft outreach, mint embed tokens, and more, all on behalf of the human asking. Every tool call lands in your audit log with `actor_type = mcp`. MCP works on every plan, including during the trial; feature-tier gates (e.g. embed tokens are Growth+) still apply regardless of how the call arrives.

> Machine-readable docs for LLM-driven IDEs and agents: this page as raw Markdown at [https://rekomi.com/docs/developers/mcp.md](https://rekomi.com/docs/developers/mcp.md), regenerated on every release.

```
AI client  ──Bearer rk_mcp_xxx──►  mcp.rekomi.com/api/mcp
                                        │
                                        │ Forwards + co-signs:
                                        │   Authorization: Bearer rk_mcp_xxx
                                        │   X-Rekomi-MCP-Identity: t=<unix>,v1=<hmac>
                                        ▼
                                   api.rekomi.com/api/v1/*
                                        │
                                        │ Validates the HMAC, validates the token,
                                        │ checks role + scope, runs the action,
                                        │ logs to audit log with actor_type=mcp.
                                        ▼
                                   Your data
```

The token alone is useless. The backend rejects any direct call to `api.rekomi.com` with an `rk_mcp_*` token with a generic 401 `{ "error": "unauthorized", "message": "Missing or invalid credentials." }`. The trusted relay at `mcp.rekomi.com` is the only path that produces the HMAC the backend requires.

Transport is MCP Streamable-HTTP in stateless mode: each tool call is a self-contained JSON-RPC request/response over HTTPS. No long-lived sessions, no SSE upgrade. A `GET https://mcp.rekomi.com/api/mcp` returns a small JSON identity blob you can use as a health check.

## Available tools

There are 61 tools (35 reads and 26 safe writes). Each tool maps 1:1 to one Rekomi API endpoint (or, for the install-recipe tools, to a local content registry). Composition (e.g. "approve every pending affiliate") happens in your AI client's reasoning loop, not inside a tool, so every individual action is surfaced to you for approval and lands in the audit log on its own row.

| Tool | Inputs | What it does |
|---|---|---|
| `list_affiliates` | `cursor?`, `take?` (1 to 200, default 50), `campaignId?` | List affiliates across your account. Cursor-paginated. Filter by campaign. |
| `get_affiliate` | `id` | Fetch one affiliate's full detail (totals, recent conversions, notes). |
| `list_conversions` | `cursor?`, `take?`, `affiliateId?`, `campaignId?`, `status?` (`Pending`/`Approved`/`Paid`/`Refunded`/`Denied`/`Invalidated`), `kind?` (`sale`/`lead`/`click`), `since?` (ISO8601), `subId?` (a channel tag, or `untagged`) | List conversions (sales, leads and click rewards). Cursor-paginated. `Invalidated` is the fraud-quarantine queue. |
| `record_lead` | `affiliateSlug`, `email`, `name?`, `campaignId?`, `subId?` | Record a free signup as a lead (an email tied to the referral, before payment), so a later sale is still credited even if the click cookie is gone. For Stripe, Paddle, Chargebee, Recurly, and Creem, leads are captured automatically; use this tool for other processors or free-account signups. Requires lead tracking to be enabled for your account. See [Track leads and signups](/docs/developers/lead-tracking). |
| `list_campaigns` | `cursor?`, `take?` | List referral campaigns with commission/payout rules and affiliate count. |
| `get_campaign` | `id` | Fetch one campaign with commission/payout rules and recent stats. |
| `list_payouts` | `cursor?`, `take?`, `status?` (`Pending`/`Processing`/`Paid`/`Failed`), `affiliateId?`, `since?` | List payouts, one row per affiliate per run: `amountCents` + `currency` (USD on consolidated runs), `method` (`StripeConnect`, `PayPal`, or `GlobalPayouts`), status and timestamps, and per-conversion `lineItems` with the native amount, the FX rate as native major units per 1 USD, and the rate date. No batch total or recipient count: sum the rows you need. |
| `get_payout` | `id` | Fetch one affiliate's payout by id with its per-conversion line items (native amount, FX rate, rate date, and any reversed amount). |
| `dashboard_summary` | `range?` (`7d`/`30d`/`90d`/`ytd`/`all`, default `30d`) | At-a-glance KPIs (revenue, conversions, leads, active affiliates, pending approvals). |
| `dashboard_timeseries` | `metric` (`revenue`/`conversions`/`clicks`/`leads`/`commissions`/`payouts`/`refunds`), `range?` (`7d`/`30d`/`90d`/`ytd`, default `30d`) | Daily timeseries for charting. ≤ ~365 points (longer windows bucket monthly). Money points may carry an additive `unconvertibleByCurrency` map (native minor units per currency held out of the bucket because no usable rate existed; `null` on count metrics). |
| `list_audit_log` | `page?` (1-based, default 1), `pageSize?` (max 100, default 50), `action?`, `entityType?`, `since?`, `until?` | Audit entries. **Offset-paginated** (not cursor). Returns `actorType` (`user`/`api_key`/`mcp`/`system`/`admin`), `action`, `entityType`, `entityId`, `before`, `after`, `createdAt`. |
| `approve_affiliate` | `id` | Approve a pending application. Sends the magic-link welcome email. |
| `reject_affiliate` | `id`, `reason?` (max 1000 chars), `notify?` (default `true`) | Reject a pending application. Optionally notifies the applicant. |
| `preview_payout` | `campaignId?`, `since?`, `affiliateId?` | Compute what a payout run would pay without running it: per-affiliate readiness (`payoutReady`, `blockedReason`) and per-currency native sums, `eligibleByCurrency` (all candidates) and `nativeTotalsForRun` (ready candidates, the map a run confirms). Payouts settle in USD: a run mints one USD payout per affiliate from the confirmed currencies, converting each commission at the stored run-date reference rate; `usdEstimateAtRunRateCents` is a display-only USD estimate at today's stored rates, and the currencies in `fxUnavailableCurrencies` have no fresh rate, are held out of the run, and keep accruing. Safe to call repeatedly. Actually running a payout stays dashboard-only (never possible via MCP or API keys). |
| `mint_embed_token` | `affiliateId`, `expiresInHours?` (1 to 720, default 24), `allowedOrigins?` (string or array of URLs), `rotate?` | Issue a short-lived affiliate-dashboard embed token. **Growth+ only**; Starter/Trial receive `plan_tier_required` (402). |
| `get_home_currency` | (no inputs) | Read the org's HomeCurrency setting plus the supported ISO 4217 allowlist + whether the caller's plan permits changing it. |
| `set_home_currency` | `homeCurrency` (3-letter ISO 4217) | Change the org's HomeCurrency. **Pro only**; lower tiers receive `plan_tier_required` (402). Idempotent on identical before/after; audit-logged. Historical conversions keep their native currency. |
| `get_program_sub_affiliate_config` | `programId` | Read a campaign's sub-affiliate (2-tier override, one recruiting level) settings: `{ enabled, percent }`. |
| `set_program_sub_affiliate_config` | `programId`, `enabled`, `percent` (0 to 100) | Enable / disable sub-affiliate recruiting on a campaign and set the override percent. Included on every plan. Admin or Owner role + verified email required. Audit-logged. |
| `list_affiliate_sub_affiliates` | `affiliateId` | Brand-side drilldown: list the sub-affiliates recruited by a specific affiliate, with each recruit's status, total commission generated, and override earned. Flat array (no pagination). |
| `list_campaign_coupons` | `programId` | List all coupon codes assigned at the campaign level. |
| `create_campaign_coupon` | `programId`, `name`, `discountType` (`PercentOff`/`AmountOff`), `discountValue`, `discountCurrency?`, `duration`, `durationInMonths?`, `maxRedemptionsPerCode?`, `expiresAt?`, `selfMintMode?` (`Disabled`/`AllAffiliates`/`SpecificAffiliates`; legacy `allowAffiliateSelfMint?`), `grantedAffiliateIds?`, `maxCodesPerAffiliate?`, `productRefs?` (Stripe `prod_` ids, max 100), `overridesClickAttribution?`, `commissionOverrideType?` + `commissionOverrideValue?` (flat rate for codes minted off this coupon), `choices?` (2-4 discount/commission splits the affiliate picks from at mint time) | Create a campaign-level coupon. Commission override and choices are mutually exclusive. |
| `list_affiliate_coupons` | `affiliateId` | List per-affiliate coupon codes for an affiliate. |
| `list_my_coupons` | (no inputs) | Affiliate-side: list the calling affiliate's self-mintable campaigns and existing per-affiliate coupon codes. Resolves the caller by user identity, which a brand connection token does not carry, so on a standard brand MCP connection this tool returns an empty result. |
| `generate_affiliate_coupon` | `affiliateId`, `campaignCouponId`, `code?`, `choiceId?` (required for choice coupons) | Mint a per-affiliate coupon code for an affiliate on a self-mint-enabled campaign. |
| `revoke_affiliate_coupon` | `affiliateId`, `couponId` | Revoke (deactivate) a per-affiliate coupon code. |
| `get_org_profile` | (no inputs) | Read the calling org's profile: name, slug, brand color, logo, description, plan tier, subscription status, trial end, payout grace days. Useful as a "who am I?" check at the start of any assistant conversation. |
| `get_billing_state` | (no inputs) | Read the org's billing state. Returns `planTier`, `subscriptionStatus`, `trialEndsAt`, `stripeCustomerLinked`, `stripeConnectLinked`, plus a plan-limits snapshot. The `stripeConnectLinked` boolean is the deciding factor for install path (Stripe-webhook vs pixel vs S2S). |
| `list_install_platforms` | (no inputs) | List every platform with a Rekomi install recipe (41 today). Returns `platformKey`, `displayName`, `category`, `conversionFire` (`stripe-webhook`, `s2s`, `crm-lookup` for platforms whose CRM connection credits sales, or the legacy `browser-pixel`), `requiresStripeConnect`, `requiresApiKey`. Use this to map the user's "I use Webflow" to a platform key. |
| `get_install_recipe` | `platformKey` | Fetch the full structured install recipe for one platform: ordered steps, common gotchas, conversion-fire path, and the canonical docs URL. Unknown keys return a generic 4-path fallback recipe (processor webhook, coupon codes, S2S, or the platform connection) instead of failing. |
| `create_campaign` | `name`, `slug`, `description?`, `termsUrl?`, `defaultCommissionType?` (`Percentage`/`Fixed`), `defaultCommissionValue?`, `defaultCommissionModel?` (`Cps`/`Cpc`/`Cpl`; legacy `Cpa`/`RevShare`/`Hybrid` still accepted), `cookieWindowDays?`, `attributionModel?`, `autoApprove?`, `networkVisibility?`, `budgetCents?` (cap on click + lead spend for any campaign that pays per click or per lead, in minor units of the org home currency; strongly recommended, without it spend is uncapped; at most USD 100,000,000 equivalent at the stored rate, else `400 budget_out_of_range`), `referralDestinationUrl?` (Cpc redirect target), `minPayoutCents?`, `leadBountyCents?` (per-lead leg on any primary except Cpl; minor units of the org home currency, USD 0.10 to USD 1,000 equivalent at the stored rate, else `400 lead_bounty_out_of_range`; never clamped), `clickRewardCents?` (per-click leg on any primary except Cpc; minor units, USD 0.01 to USD 1,000 equivalent, else `400 click_reward_out_of_range`; makes the campaign invite-only), `saleCommissionType?` + `saleCommissionValue?` (per-sale leg on a Cpc/Cpl primary; renewals follow `recurringMonths`), `fraudStrictness?` (`Strict`/`Standard`/`Relaxed`, campaigns with a per-click or per-lead leg only), `applicantScreening?` (`Off`/`Low`/`Medium`/`High`), `requireReviewOnVpn?`, `adTrafficPolicy?` (`Allow`/`Review`/`Block`), `showLeaderboard?`, `allowedCountries?` (ISO alpha-2 list), `recurringMonths?`, `recurringDelayInvoices?` | Create a new campaign (referral program). Backend enforces commission type/model pairing (RevShare requires Percentage type, Cpc/Cpl require Fixed). A `Fixed` `defaultCommissionValue` is bounded in USD-equivalent terms at the stored rate: per-click / per-lead fees at most USD 1,000 (`400 unit_fee_out_of_range`), per-sale flat amounts at most USD 1,000,000 (`400 fixed_commission_out_of_range`). Audit-logged. |
| `get_budget_status` | `campaignId` | Read a campaign's click + lead spend-cap status: total budget, spent, and remaining (minor units of the org home currency). Unlimited if no budget set. Spend whose currency has no usable stored rate is held out of `spentCents` and listed in `fxUnconvertibleByCurrency` (native minor units per code), the authoritative figure; `fxUnconvertibleCents` is a sum of native minor units across currencies, a presence signal rather than a money amount. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. |
| `list_fraud_events` | `campaignId?`, `status?` (`open`/`all`) | List fraud signals flagged on paid clicks and leads (already dropped, no payout): high-risk IPs, bots, self-leads, disposable emails, per-IP-cap hits, abusive affiliates. The review queue. Applies to per-click and per-lead campaigns. |
| `review_fraud_event` | `id`, `action` (`resolve`/`dismiss`) | Review a fraud signal: mark resolved or dismiss as a false positive. Manager role. A write tool: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. |
| `ban_affiliate` | `affiliateId` | Ban a partner: stops all their future clicks, leads, and conversions from earning. Manager role. Destructive: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. |
| `validate_conversion` | `id` | Release a fraud-quarantined (`Invalidated`) conversion back to payable (clear a false positive). Manager role. A write tool: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. |
| `invalidate_conversion` | `id` | Quarantine a `Pending`/`Approved` conversion you judge fraudulent (sets `Invalidated`, not paid). Manager role. Withholds payment: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. |
| `create_affiliate_invite` | `campaignId`, `email`, `fullName?`, `slug?`, `personalNoteHtml?`, `customCommissionType?`, `customCommissionValue?`, `autoApprove?` (default `true`) | Send a branded invite email to one affiliate for a campaign. Available on every plan including Trial. Personal note HTML is server-sanitized to a strict allowlist. **MIGRATION**: pass `slug` to preserve the affiliate's existing slug from a prior platform so their existing public referral URL keeps working. Returns 409 `slug_already_taken` on collision. |
| `bulk_invite_affiliates` | `campaignId`, `recipients[]?` (preferred) OR `emails[]?` (legacy, 1-50), `personalNoteHtml?`, `autoApprove?` | Bulk-invite up to 50 affiliates at once. **Requires Starter tier or higher with an active paid subscription** (Trial users receive `paid_plan_required` HTTP 402). The `recipients` shape carries per-row `email`, `slug?`, `fullName?` so migration imports preserve every affiliate's existing slug. Response includes `slugConflicts[]` for rows where the requested slug was rejected; those still get an invite with a fresh random slug. |
| `import_affiliates` | `campaignId`, `affiliates[]` (1-1000; per row `email`, `fullName?`, `status?`, `paypalEmail?`, `slug?`), `includeStatuses?`, `notifyAffiliates?`, `notificationMessageHtml?` | Bulk-import an affiliate roster from canonical JSON rows (platform-agnostic; you map any export into Rekomi fields). Dedupes by email within the campaign and the batch (idempotent). `status` defaults to `approved`. Starter+ tier for direct API-key callers (402 plan_tier_required); MCP-relayed calls bypass the plan gate. `notifyAffiliates` additionally needs Growth+ with an active subscription. Returns a revertible `jobId` + `{ imported, skipped, errored, reasons }`. A write tool: confirm before running. |
| `preview_conversion_import` | `campaignId`, `conversions[]` (1-1000; canonical rows) | **Dry-run** a historical conversion/commission import. Writes nothing. Returns `{ rowsTotal, wouldImport, wouldSkip, wouldError, skipReasons, paidBalance, unpaidBalance }`. Always run this before `import_conversions` and show the user `unpaidBalance` (how much would become payable). |
| `import_conversions` | `campaignId`, `conversions[]` (1-1000; per row `affiliateEmail?`/`affiliateSlug?` [one required], `customerEmail?`, `customerName?`, `amountCents`, `commissionCents`, `currency`, `occurredAt`, `paid`, `type?`, `stripeCustomerId?`, `stripeSubscriptionId?`, `externalId?`), `confirmUnpaidPayout?` (default `false`), `excludePaid?` | Import historical conversions/commissions. Idempotent (re-running never double-pays). **Money safety:** paid rows lock as settled history; unpaid rows become **payable only when `confirmUnpaidPayout=true`** (defaults false). Starter+ tier for direct API-key callers (402 plan_tier_required); MCP-relayed calls bypass the plan gate. A write tool that can create payable balances: always preview first, then confirm with the user before setting `confirmUnpaidPayout=true`. Returns a revertible `jobId` plus `unpaidConfirmed` (payable commission per currency as `{ currency, commissionCents, count }`, empty unless confirmed) and `unpaidConfirmedCurrency` (set only when exactly one currency became payable); `unpaidConfirmedCents` is a raw cross-currency sum kept for older callers and is a money amount only when `unpaidConfirmedCurrency` is set. |
| `get_attribution_params` | (no inputs) | Read the org's custom attribution params + the 21 canonical defaults + the effective priority order the loader uses. Useful for confirming whether a migrating brand needs custom-param config or whether their source platform is already covered by the defaults. |
| `update_attribution_params` | `custom?` (comma-separated string, max 10 entries) | Set the brand's custom attribution params. Each entry `[a-z0-9_-]{2,32}`, max 10 entries, max 256 chars total. Empty / omit clears the override. Admin-or-Owner write (audit-logged). |
| `update_campaign` | `campaignId` + any of: `name?`, `description?`, `termsUrl?`, `isActive?`, `defaultCommissionType?`, `defaultCommissionValue?`, `defaultCommissionModel?`, `cookieWindowDays?`, `recurringMonths?` (`-1` clears to lifetime), `recurringDelayInvoices?`, `attributionModel?`, `autoApprove?`, `networkVisibility?`, `isPubliclyDiscoverable?`, `referralDestinationUrl?` (`""` clears), `budgetCents?` (`0` clears; otherwise the same USD 100,000,000 equivalent ceiling as create), `minPayoutCents?` (`0` clears), `leadBountyCents?` (`0` clears; otherwise the same USD 0.10 to USD 1,000 equivalent band as create), `saleCommissionType?` + `saleCommissionValue?` (`0` clears the pair), `fraudStrictness?`, `applicantScreening?`, `requireReviewOnVpn?`, `adTrafficPolicy?`, `showLeaderboard?`, `allowedCountries?` (`[]` clears) | Update an existing campaign. Omitted fields are unchanged. A write tool: confirm first. |
| `update_affiliate` | `id` + any of: `fullName?`, `status?` (`Pending`/`Approved`/`Rejected`/`Paused`/`Banned`), `customCommissionType?` + `customCommissionValue?`, `clearCustomCommission?`, `notes?`, `tags?` | Update one affiliate: status, per-affiliate commission override, private notes, tags. A write tool: confirm first. |
| `list_affiliate_applications` | `campaignId?`, `take?` (default 200, max 500) | The pending-approval queue, including each applicant's answers (website, audience, why-interested). Flat array. Pair with `approve_affiliate` / `reject_affiliate`. |
| `create_conversion` | `campaignId`, `affiliateId`, `kind?` (`sale`/`bonus`), `amountCents?`, `currency?`, `commissionCents?`, `customerEmail?`, `occurredAt?` (≤ 5 years back), `note?`, `subId?` | Manually record an off-platform sale or a one-off affiliate bonus. Server caps: `amountCents` and a bonus `commissionCents` at most 1,000,000,000,000 minor units of the currency (a currency-blind sanity bound, the same in every currency); a sale `commissionCents` at most 5× `amountCents`. No built-in dedup. Creates a payable balance: always confirm first. |
| `dashboard_leaderboard` | `metric` (`clicks`/`conversions`/`earnings`/`epc`), `limit?` (max 50), `range?`, `campaignId?` | Rank affiliates over a window. |
| `dashboard_traffic_sources` | `range?`, `campaignId?`, `affiliateId?` | Where clicks come from, channel-classified: each of the top-20 sources carries a `channel` (`paid`/`search`/`social`/`email`/`ai`/`signin`/`onsite`/`portal`/`bot`/`referral`/`direct`) plus count and percentage. |
| `get_insights` | `module` (`overview`/`sale-origin`/`top-products`/`geography`/`devices`/`utm`/`time-patterns`/`time-to-convert`/`funnel`/`attribution`/`recurring`/`customers`), `range?`, `campaignId?`, `affiliateId?`, `dimension?` (utm only: `source`/`medium`/`campaign`/`subid`) | Deep-dive analytics, one module per call. PII-free aggregates, top-20 capped. Money is FX-normalized into the home currency; rows with no usable stored rate are held out and, on `overview`, listed per currency in native minor units in `fxUnconvertibleByCurrency` (revenue) and `fxUnconvertibleCommissionByCurrency` (commission), the authoritative figures; `fxUnconvertibleCents` is a sum of native minor units across currencies, a presence signal rather than a money amount. |
| `get_fraud_summary` | `campaignId`, `days?` (1-365, default 30) | Aggregate fraud stats for a campaign that pays per click or per lead: blocked clicks by reason, held clicks, quarantined leads, and estimated commission not billed. |
| `list_uncredited_conversions` | `status?` (`open`/`dismissed`/`all`), `affiliateId?` | Sales that arrived attributed to an affiliate who could not earn at that moment (pending/paused/rejected/banned at sale time), with amounts and reasons. The "did we miss paying anyone?" queue. |
| `dismiss_uncredited_conversion` | `id` | Mark an uncredited-conversion row reviewed (rightly not credited). Moves no money. |
| `get_tracking_status` | `campaignId` | Install verification: whether the campaign has ever recorded a click or conversion, last-seen timestamps, and 30-day totals, including `creditedByPlatform30d` (sales credited through a CRM connection's lookup). The per-email lookup can answer `attributed_by_platform`. Call after an install walkthrough to confirm tracking works. |
| `list_crm_integrations` | (no inputs) | Read the brand's connected CRM platforms with both switches: `enabled` (Sync affiliates), `salesAttribution` (`none`, `native` or `custom_field`), `salesAttributionEnabled` (Credit sales), its status and last check, the lifetime count of sales credited through the platform, and whether the signup webhook is registered. Read-only. Call it before recommending an install path on beehiiv, Kit, MailerLite, Ontraport or GoHighLevel. |
| `list_deals` | none | Creator deals with their milestone schedules (status, amounts, fee snapshots, proof-of-delivery links, terms, open disputes, change orders). Funding and releasing stay in the dashboard. |
| `get_deal` | `id` | One deal with its full schedule, disputes, change orders, and terms. |
| `approve_milestone` | `dealId`, `milestoneId` | Approve a delivered milestone (Funded or Submitted to Approved). Moves no money; the brand releases from the dashboard or it auto-releases at the end of the review window. |
| `request_milestone_changes` | `dealId`, `milestoneId`, `note?` | Ask the creator for changes on a submitted milestone; pauses its auto-release clock until they resubmit (5 rounds max). |
| `get_install_snippet` | `campaignId?`, `withPixel?`, `emailCapture?` | Get the ready-to-paste install snippet for the brand with program id + custom attribution params + optional pixel public key already embedded. Email capture is on by default: the tag carries `data-rkmi-email-capture="auto"` and the tool prints next to it what that records (an email a referred visitor types into any email or text field on the pages where the tag runs; nothing without a referral) and how to turn it off; `emailCapture=false` renders the bare tag. Single source of truth for snippet generation; agent hands the response verbatim to the user. Defaults to the newest active campaign, no pixel. Returns 409 when `withPixel=true` and the org hasn't provisioned a pixel public key. |

Cursor-paginated list responses always look like `{ "items": [...], "nextCursor": "…", "hasMore": true }`. `list_audit_log` returns `{ "page", "pageSize", "total", "items" }` instead.

The assistant calls each tool individually and surfaces its result back to you in chat. Destructive or money-affecting tools (`approve_affiliate`, `reject_affiliate`, `import_affiliates`, `import_conversions`) should be confirmed by the user before the assistant runs them. For a conversion import, always run `preview_conversion_import` first and review the unpaid balance before authorizing payment with `confirmUnpaidPayout`.

## Install Rekomi with the assistant

The most common use of the MCP server is what it sounds like: "help me install Rekomi on my site." A typical session looks like this.

1. You start a chat with Claude (or Cursor, or any MCP-capable client) and say "Help me install Rekomi on my Shopify store."
2. The assistant calls `get_org_profile` to confirm who you are and what plan you're on.
3. It calls `get_billing_state` to see whether you've connected Stripe yet. If `stripeConnectLinked` is true, it recommends the stripe-webhook path; if false, it nudges you to a native gateway integration, coupon codes, or S2S. On beehiiv, Kit, MailerLite, Ontraport or GoHighLevel it calls `list_crm_integrations` first, because the platform connection with Credit sales on is the install there.
4. It calls `list_install_platforms` and matches "Shopify" to `shopify`.
5. It calls `get_install_recipe` with `platformKey: shopify` and walks you through the steps in chat. It surfaces the gotchas inline (the native Rekomi Shopify app is the no-code path; on the manual fallback, Shopify Payments hides your Stripe and the Custom Pixel sandbox cannot load external scripts; Shopify Collabs is a separate product).
6. If you haven't created a campaign yet, the assistant offers to call `create_campaign` and asks you to confirm the name, slug, commission type, and value before doing so.
7. If you're migrating from another platform, prefer the dedicated importers under Settings, Migrate - there are 22 (Rewardful, Affonso, Tapfiliate, FirstPromoter, PartnerStack, Dub Partners, Tolt, Lemon Squeezy, Gumroad, impact.com, PromoteKit, LeadDyno, Trackdesk, Partnero, UpPromote, GoAffPro, ReferralCandy, Refersion, BixGrow, Shopify Collabs, Everflow, and Post Affiliate Pro) (the Rewardful, FirstPromoter, and Tolt API pulls also bring leads, lifetime click counts, and commission history; Gumroad's sales history pulls from its API after the roster CSV). For an assistant-driven flow, paste your existing affiliate emails into the chat. The assistant uses `bulk_invite_affiliates` to send branded onboarding emails to all of them at once. (Requires Starter tier or higher with an active paid subscription.)

### What the assistant cannot do (security)

For the install flow to be safe, the MCP server intentionally does not expose certain operations as tools:

- **The assistant cannot mint API keys.** If your install needs a `rk_live_*` bearer + `rks_*` signing secret (Gumroad / Rails / Django / any S2S path), the assistant will deep-link you to **Settings → API keys** to mint one yourself, then ask you to paste the key back. The MCP token cannot escalate into a long-lived API key that outlives the MCP token itself.
- **The assistant cannot create new MCP tokens.** Token minting is owner-only and JWT-only.
- **The assistant cannot run, retry, or reverse payouts.** Money movement requires an interactive dashboard session; MCP and API keys both receive 403 `interactive_session_required`. The assistant can only `preview_payout`.
- **The assistant cannot change billing or open Stripe Checkout sessions.** Billing decisions stay with the human owner; the assistant can only read billing state via `get_billing_state`.

If a tool seems missing, that's the reason. The capability is intentionally not exposed.

## Plan-gate semantics

There are two different plan gates in the Rekomi backend:

- **Public-API gate**: applies per endpoint to direct API-key (`rk_live_*`) callers. Most of the `/api/v1/*` surface requires Starter or higher, which every current plan meets; a few endpoints such as audit-log export require Pro. Calls relayed through the MCP server **bypass** this gate entirely, so every MCP tool works on any plan.
- **Feature-tier gate**: blocks lower plans from a specific feature regardless of how it's called. MCP does **not** bypass this. Today this applies to `mint_embed_token` (Growth+) and a handful of other surfaces; a Starter user calling `mint_embed_token` over MCP gets `plan_tier_required` (HTTP 402).

If a tool returns `plan_tier_required`, the answer is to upgrade the plan, not to change tokens.

## Security model

- **Token shape**: `rk_mcp_*`, 256-bit random body, base64url-encoded. Stored as SHA-256 hash; plaintext never persisted.
- **One-time reveal**: the full token is shown only at creation or rotation. Rotate to invalidate the old value.
- **Owner-only minting**: only an organization owner can create, rotate, or revoke MCP tokens.
- **Per-org cap**: 10 active tokens per organization.
- **Read+write scope**: every MCP token is granted the `read_write` scope; the per-tool user-consent loop in your AI client is the user-facing approval gate.
- **Direct use blocked**: a stolen `rk_mcp_*` is useless without the trusted relay. Direct hits to `api.rekomi.com` are rejected with a generic 401 `unauthorized`; only requests through `mcp.rekomi.com` carry the signed relay header the API requires.
- **Relay binding**: the relay HMAC is computed over `{t}.{METHOD}.{path}.{body_sha256_hex}` with a shared secret. ±300 s timestamp window, constant-time compare. Replays, body tampering, and method/path swaps all fail validation.
- **Revoke propagation**: revoked tokens stop authenticating within ~10 s across all instances (bounded by the auth cache TTL).
- **Audit trail**: every tool call lands in your audit log with `actor_type = mcp`, the calling token id, action, entity, and timestamps. Filter by `actor_type = mcp` under **Settings → Audit Log**.
- **Suspension gate**: a suspended account stops every MCP call within seconds.
- **PII scrubbing**: tokens and the relay-identity header are scrubbed from MCP-server-side error reporting before they leave the host.

## Best practices

- Use a separate token per device or per AI client. If a laptop is lost, revoke just that token.
- Set up read-only assistant agents by instructing the assistant not to call write tools (the twenty-six writes are `approve_milestone`, `request_milestone_changes`, `approve_affiliate`, `reject_affiliate`, `preview_payout`, `mint_embed_token`, `set_home_currency`, `set_program_sub_affiliate_config`, `create_campaign_coupon`, `generate_affiliate_coupon`, `revoke_affiliate_coupon`, `create_campaign`, `update_campaign`, `update_affiliate`, `create_conversion`, `create_affiliate_invite`, `bulk_invite_affiliates`, `update_attribution_params`, `import_affiliates`, `import_conversions`, `record_lead`, `review_fraud_event`, `ban_affiliate`, `validate_conversion`, `invalidate_conversion`, `dismiss_uncredited_conversion`). The audit log still records the actor as the assistant.
- Watch your audit log under **Settings → Audit Log** during the first few days. Filter by `actor_type = mcp` to see exactly what the assistant has done.
- Don't paste the same token into multiple clients you can't independently revoke later, one token per surface keeps blast radius bounded.

## Troubleshooting

- **401 `unauthorized` on a direct API call**, your AI client is hitting `api.rekomi.com` directly with an MCP token; direct calls are rejected with the generic 401. Point it at `https://mcp.rekomi.com/api/mcp` instead.
- **`plan_tier_required` (HTTP 402) on a specific tool**, the tool is feature-gated to a higher plan (e.g. `mint_embed_token` is Growth+). Upgrade the plan or switch to a tool that's available on your tier. Switching tokens will not help, feature gates apply to MCP traffic too.
- **`unauthorized` (HTTP 401) on every call**, the token is wrong, revoked, or rotated. Mint a fresh one under **Settings → AI assistant**.
- **Tools list works but every tool returns 401**, the backend cannot validate the relay header. Most often a stale relay secret on one side; contact support.
- **Claude Code still sends the old token after I rotated**, Claude Code stores the bearer header statically in `~/.claude.json`. Rotating the token in the Rekomi dashboard does not propagate. Run `claude mcp remove rekomi` and then `claude mcp add …` again with the new token.
- **Claude Code logs `SDK auth failed: JSON Parse error`**, harmless. Some MCP clients probe `/.well-known/oauth-protected-resource`; the server returns a clean JSON 404 and continues with bearer auth.
- **`/mcp` "Re-authenticate" doesn't do anything**, that menu item handles OAuth-flow servers; Rekomi uses a static bearer header. Use `claude mcp remove` + `claude mcp add` to swap credentials.

## See also

- [Public API reference](/docs/developers/overview)
- [Audit log](/docs/brands/settings#audit-log)
- Machine-readable manifest: [rekomi.com/docs/developers/mcp.md](https://rekomi.com/docs/developers/mcp.md)
