Referral programs

Embed
Preview of the card
<a href="https://forumulate.com/guides/referral-programs"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/b9c340e8-fb79-457d-b565-c5f4fb054546/social-card.svg?v=3e7ae33eec8b" alt="Referral programs" width="600" height="315"></a>

Connect the referral or affiliate program you already run — Referalli, Rewardful, PartnerStack, FirstPromoter, Tolt, Impact, or one of your own — and your pages, sign-up and community work with it. Every program is the same provider model; the presets only pre-fill where the vendor's tracking script lives, what its link parameter is called and how its API key is checked.

Connect a program

PUT /orgs/{orgId}/referrals/providers/{provider}:

{
  "trackingToken": "pub_123",
  "apiKey": "sk_…",
  "config": { "landingUrl": "https://acme.com/partners" }
}
  • trackingToken and apiKey are write-only: a value sets, null clears, leaving it out keeps. Reads say only hasTrackingToken / hasApiKey.

  • A partner link instead of the API key. If the program issues partner links (Referalli does), send "partnerLink": {"grantId": "pl_…", "secret": "…"} and leave apiKey out. We exchange the grant at the program's token endpoint for a short-lived token on each call, so no long-lived key of yours sits with us. Another vendor names its endpoint with tokenUrl. Write-only like the key; reads answer partnerLink: {grantId, tokenUrl}. See Partner links we hold.

  • config.landingUrl is where :::referral links go.

  • config.refParams overrides the parameter names the preset keeps.

  • For custom: config.scriptSrc (https), tokenAttribute and connectOrigins.

The older preset spelling referally is accepted and stored as referalli.

What your pages do

  • Keep the partner's parameter. A reader who lands with ?ref=partner42 (or the program's own name for it) keeps it on every link inside your site and on the link to your sign-in page, so the partner is still named when they sign up. Nothing is written to cookies or storage for this.

  • Load the program's script only with consent. The tracking script loads once the reader allows the suite's referrals category (a consent platform without it answers as marketing). Its origin is added to the page's content security policy for you.

  • Link to the program. A :::referral block anywhere on a page:

:::referral{label="Join the partner program" campaign="docs"}:::

:::referalli{…}::: is read as the same block (it is written back as :::referral), so a snippet copied from a referral program's own docs works unchanged.

It links to href if you give one, else to the program's landing URL, with campaign= added, marked rel="sponsored". In the editor it is "Referral link" in the slash menu.

Credit a partner's standing

A program can show a partner's tier in your community without knowing their user id here: PUT /orgs/{orgId}/community/reputation/by-subject/external with ?iss=&sub= (their sign-in subject at your identity provider) and {source, label, value}. A subject who has not joined yet is held (202 pending) and applied when they do. A partner link with community:standing:write may call it.

Webhooks topic.solved, post.created and user.joined carry author: {iss, sub, ext} so a program can match the author to its own records.

If you are a Forumulate partner yourself, set general.powered_by_referral_url (Settings → General) to your partner link and the "Published with Forumulate" credit points there instead, marked as sponsored. It is off until you set it.

Showing pages to some readers only

Readers who sign in through your identity provider can carry a principal (partner, customer), roles and personal vars. Map where your IdP puts them with the sign-in provider's claimMap.

  • settings.audience on a space — {"principals": ["partner"]} — limits the whole space; a collection's nav.audience limits one collection; settings.postingAudience limits who may post in each forum category ("*" for all). A reader who does not match gets a 404, as if the page did not exist, and these pages stay out of sitemaps and llms.txt.

  • your-program in a page — code samples included — becomes the reader's own value, with the fallback for everyone else.

Personal values: where they are filled

Where the page is read

your-program becomes

The page, signed in

The reader's own programId

The page's .md twin (…/<page>.md, View as Markdown, Copy page), signed in

The reader's own programId

The page and its .md twin, signed out

your-program

llms.txt, llms-full.txt, the knowledge feed, PDFs, search

your-program — these copies are the same for everyone

?form=source (the page as written, for editing tools)

The template itself, unchanged

A var with no fallback (YOUR_PROGRAM_ID) becomes a placeholder named after it, YOUR_PROGRAM_ID, in every shared copy — the same convention as YOUR_API_KEY in generated code samples. The raw {{…}} template is never served. A page or .md twin that uses reader vars is answered Cache-Control: private, no-store, so no cache in front of us keeps one reader's values for another.

"Try it" starts from the reader's values. On an API reference page, a signed-in reader's vars prefill the console: a var named apiKey (or api_key, apiToken, accessToken, token) becomes the operation's credential, and any other var fills the parameter of the same name (programId → {programId}; case, _ and - are ignored). Fields the reader has typed in are never overwritten. The values come from the reader's own session (GET /public/orgs/{orgId}/spaces/{spaceId}/viewer → vars, answered only to that reader) and stay in their browser.

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go SDKs are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP agent server — its api_* family tools, or call_api. Where a CLI command or a hand-made MCP tool exists too, the last column names it. Operation names link to the API reference.

Referrals

Operation

Request

What it does

Shortcuts

Referrals_presets

GET /orgs/{orgId}/referrals/presets

List referral program presets

client.referrals.presets()

Referrals_list

GET /orgs/{orgId}/referrals/providers

List connected referral programs

client.referrals.list()

Referrals_get

GET /orgs/{orgId}/referrals/providers/{provider}

Get a connected referral program

client.referrals.get()

Referrals_put

PUT /orgs/{orgId}/referrals/providers/{provider}

Connect or update a referral program

client.referrals.put()

Referrals_patch

PATCH /orgs/{orgId}/referrals/providers/{provider}

Change part of a referral program

client.referrals.patch()

Referrals_remove

DELETE /orgs/{orgId}/referrals/providers/{provider}

Disconnect a referral program

client.referrals.remove()

Referrals_verify

POST /orgs/{orgId}/referrals/providers/{provider}/verify

Check a referral program’s API key

client.referrals.verify()

Reputation Subject

Operation

Request

What it does

Shortcuts

ReputationSubject_set

PUT /orgs/{orgId}/community/reputation/by-subject/external

Set a subject’s external standing

client.community.reputationSubject.set()

ReputationSubject_remove

DELETE /orgs/{orgId}/community/reputation/by-subject/external

Remove a subject’s external standing

client.community.reputationSubject.remove()

ReputationSubject_pending

GET /orgs/{orgId}/community/reputation/by-subject/external/pending

List standing waiting for its subject

client.community.reputationSubject.pending()

Viewer

Operation

Request

What it does

Shortcuts

Viewer_viewer

GET /public/orgs/{orgId}/spaces/{spaceId}/viewer

Get the viewer in a space

client.public.viewer.viewer()

Viewer_myAccess

GET /public/orgs/{orgId}/spaces/{spaceId}/viewer/access

A reader’s access to a space

client.public.viewer.myAccess()

The Forumulate teamUpdated