API reference · App API
Affiliates
Your users as affiliates: their own code and results, codes they enter, and how your program is configured.
Claim an invite code
/v1/affiliates/claimBinds an invite code to one of your users who already has an account: a code typed in, pasted or carried by a universal link. Before sign-up, ask with POST /v1/attributions instead.
Safe to repeat. A registration that arrived earlier without a code is attributed again when the claim arrives. Pass the clickId and platform you have, so the claim can be tied to the click that led to it.
AuthSigned request with an app key, fg_live_… or fg_test_…
Request body
appUserIdstringrequiredYour pseudonymous id for the user. Never an email address or phone number.
codestringrequiredThe invite code the user typed, pasted or opened. Case and surrounding spaces do not matter.
sourcestringuniversal_link when the code came from the link that opened the app, code_entry when the user typed or pasted it.
One of code_entry universal_link
clickIdstringfg_click from the link that opened the app. A value that is not a click id is ignored, never refused.
platformstringThe platform the app runs on. Any other value is ignored.
One of ios android
Returns · 200
touchIdstringrequiredattributedbooleanrequiredWhether the user’s registration is now attributed to this code. False until user.registered has been sent and processed.
attributionKeystringrequiredThe claim’s attribution key, bound to this user. Events may carry it instead of appUserId.
Errors
- 400
validation_error The request does not match the schema.
detailssays which fields failed and why.- 401
missing_signature One of
x-fg-key,x-fg-timestampandx-fg-signatureis missing.- 401
invalid_timestamp x-fg-timestampis not Unix time in seconds.- 401
stale_timestamp x-fg-timestampis more than 300 seconds from the server clock. Sign each attempt again, and keep your clock in sync.- 401
invalid_key The key id is unknown or revoked, or the app is archived.
- 401
invalid_signature The signature does not match. Sign the exact bytes you send.
- 404
not_found No code like this belongs to your app.
- 409
code_inactive The code was deactivated or has expired.
- 409
already_attributed The user is already attributed to another referrer.
- 429
too_many_attempts Too many requests with this key in the last minute. Wait
Retry-Afterseconds.
BODY='{"appUserId":"u_8F2K","code":"k7qxa2mp","source":"code_entry","platform":"ios"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$FG_SECRET" | sed 's/^.*= //')
curl -X POST "https://api.flowgroup.se/v1/affiliates/claim" \
-H "content-type: application/json" \
-H "x-fg-key: $FG_KEY_ID" \
-H "x-fg-timestamp: $TS" \
-H "x-fg-signature: $SIG" \
--data-raw "$BODY"import { createFlowGrowth, parseReferralLink } from '@flow/growth-sdk';
const fg = createFlowGrowth({
baseUrl: 'https://api.flowgroup.se',
keyId: process.env.FG_KEY_ID!, // fg_test_… in the sandbox
secret: process.env.FG_SECRET!,
});
const hints = parseReferralLink(landingUrl); // { clickId?, affiliateCode? }
const res = await fg.claimAffiliate({
appUserId: 'u_1042',
code: 'k7qxa2mp',
source: 'code_entry', // or 'universal_link'
clickId: hints.clickId, // optional: ties the claim to the click it came from
platform: 'ios',
});
if (res.ok) console.log(res.data.attributed, res.data.attributionKey);{
"touchId": "6b0f2a51-3c4d-4e8f-9a1b-2c3d4e5f6a7b",
"attributed": true,
"attributionKey": "fga_k7qxa2mpw4ht9bvc3rnd8ezj"
}Record the first app open
/v1/affiliates/app-openReports the first open of the app after an install, with the click id or code it came with, so the funnel shows how many clicks became installs. Measurement only: it never attributes or pays. An open with neither a click id nor a code is not counted, and a sandbox key gets the same answer without recording anything.
AuthSigned request with an app key, fg_live_… or fg_test_…
Request body
clickIdstringfg_click from the link that opened the app. A value that is not a click id is ignored, never refused.
codestringfg_ref, or the code the app opened with, when there is no click id.
platformstringThe platform the app runs on. Any other value is ignored.
One of ios android
Returns · 200
matchedbooleanrequiredWhether the open was tied to a click on one of your links.
matchstringrequiredclick_id: the click id named it. nearest_click: the code led to its most recent click on this platform.
One of click_id nearest_click
Errors
- 400
validation_error The request does not match the schema.
detailssays which fields failed and why.- 401
missing_signature One of
x-fg-key,x-fg-timestampandx-fg-signatureis missing.- 401
invalid_timestamp x-fg-timestampis not Unix time in seconds.- 401
stale_timestamp x-fg-timestampis more than 300 seconds from the server clock. Sign each attempt again, and keep your clock in sync.- 401
invalid_key The key id is unknown or revoked, or the app is archived.
- 401
invalid_signature The signature does not match. Sign the exact bytes you send.
- 429
too_many_attempts Too many requests with this key in the last minute. Wait
Retry-Afterseconds.
BODY='{"clickId":"1d4f9c5e-7a8b-4c2d-9e1f-0a1b2c3d4e5f","platform":"android"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$FG_SECRET" | sed 's/^.*= //')
curl -X POST "https://api.flowgroup.se/v1/affiliates/app-open" \
-H "content-type: application/json" \
-H "x-fg-key: $FG_KEY_ID" \
-H "x-fg-timestamp: $TS" \
-H "x-fg-signature: $SIG" \
--data-raw "$BODY"import { createFlowGrowth, parseReferralLink } from '@flow/growth-sdk';
const fg = createFlowGrowth({
baseUrl: 'https://api.flowgroup.se',
keyId: process.env.FG_KEY_ID!, // fg_test_… in the sandbox
secret: process.env.FG_SECRET!,
});
const hints = parseReferralLink(landingUrl); // { clickId?, affiliateCode? }
const res = await fg.appOpen({ clickId: hints.clickId, code: hints.affiliateCode, platform: 'android' });
if (res.ok) console.log(res.data.matched);{
"matched": true,
"match": "click_id"
}Get a user’s affiliate summary
/v1/affiliates/meThe user’s own invite code and link, how many people signed up with it, and what they have earned. Creates their referrer account on first use, so call it when the user opens your referral screen.
AuthSigned request with an app key, fg_live_… or fg_test_…
Query parameters
appUserIdstringrequiredYour pseudonymous id for the user. Never an email address or phone number.
codestringThe code the user already shares in your app (3 to 24 letters and digits), kept as theirs here.
Matches ^[A-Za-z0-9]{3,24}$
Returns · 200
One of your users as an affiliate.
codestringrequiredThe user’s own invite code.
urlstringrequiredTheir shareable link for the code.
programIdstringrequiredstatusstringrequiredOne of active inactive
countsobjectrequiredChild attributes
registeredintegerrequiredUsers who signed up with their code or link.
qualifiedintegerrequiredrejectedintegerrequiredRejected or reversed.
earnedarray of objectsrequiredPer currency.
Child attributes
currencystringrequiredamountintegerrequiredEverything earned, in minor units.
pendingintegerrequiredNot delivered to the user yet.
deliveredintegerrequiredDelivered through your reward webhook.
Errors
- 400
validation_error The request does not match the schema.
detailssays which fields failed and why.- 401
missing_signature One of
x-fg-key,x-fg-timestampandx-fg-signatureis missing.- 401
invalid_timestamp x-fg-timestampis not Unix time in seconds.- 401
stale_timestamp x-fg-timestampis more than 300 seconds from the server clock. Sign each attempt again, and keep your clock in sync.- 401
invalid_key The key id is unknown or revoked, or the app is archived.
- 401
invalid_signature The signature does not match. Sign the exact bytes you send.
- 404
not_found Your app has no active friend-referral program.
- 429
too_many_attempts Too many requests with this key in the last minute. Wait
Retry-Afterseconds.
TS=$(date +%s)
SIG=$(printf '%s.' "$TS" | openssl dgst -sha256 -hmac "$FG_SECRET" | sed 's/^.*= //')
curl "https://api.flowgroup.se/v1/affiliates/me?appUserId=u_1042" \
-H "x-fg-key: $FG_KEY_ID" \
-H "x-fg-timestamp: $TS" \
-H "x-fg-signature: $SIG"import { createFlowGrowth } from '@flow/growth-sdk';
const fg = createFlowGrowth({
baseUrl: 'https://api.flowgroup.se',
keyId: process.env.FG_KEY_ID!, // fg_test_… in the sandbox
secret: process.env.FG_SECRET!,
});
const res = await fg.getAffiliateSummary('u_1042');
if (res.ok) console.log(res.data.code, res.data.url);{
"code": "k7qxa2mp",
"url": "https://go.affiliate.yflow.io/a/k7qxa2mp",
"programId": "0c9e6f3a-1b2d-4c5e-8f7a-9b0c1d2e3f4a",
"status": "active",
"counts": {
"registered": 3,
"qualified": 2,
"rejected": 0
},
"earned": [
{
"currency": "USD",
"amount": 400,
"pending": 200,
"delivered": 200
}
]
}Get the program’s mode and rules
/v1/affiliates/configWhich mode your friend-referral program is in for a market, and under which rules, so the app can show the right terms and knows whether to credit referral rewards itself.
AuthSigned request with an app key, fg_live_… or fg_test_…
Query parameters
countrystringISO 3166 alpha-2 country of the new user. Without it, the program’s default market applies.
Returns · 200
Your friend-referral program as it applies to new users in one country. Cache it for a minute.
modestringrequiredoff: Flow Affiliate is not involved, run your own scheme. shadow: keep your scheme; Flow Affiliate works out what it would pay, for comparison. live: Flow Affiliate decides; credit exactly what the reward webhook says.
One of off shadow live
programIdstringrequiredcampaignIdstringrequiredcurrencystringrequiredrulesarray of objectsrequiredThe rules in force, to show the terms to your users. Empty when mode is off.
Child attributes
idstringrequirednamestringrequiredtriggerEventstringrequiredThe event type that earns it, e.g. purchase.completed.
modelstringrequiredOne of fixed percentage
amountintegerrequiredMinor units, for fixed rules.
percentBpsintegerrequiredBasis points of the event’s amount, for percentage rules.
maxAmountintegerrequiredCeiling on one payout of a percentage rule, minor units.
currencystringrequiredbeneficiarystringrequiredpartner: whoever referred the user. referred_user: the new user themselves.
One of partner referred_user
recurringbooleanrequiredPays on every matching event, not just the first.
holdDaysintegerrequiredqualificationobjectrequiredConditions the event must meet, e.g. withinDays.
updatedAtstringrequiredErrors
- 400
validation_error The request does not match the schema.
detailssays which fields failed and why.- 401
missing_signature One of
x-fg-key,x-fg-timestampandx-fg-signatureis missing.- 401
invalid_timestamp x-fg-timestampis not Unix time in seconds.- 401
stale_timestamp x-fg-timestampis more than 300 seconds from the server clock. Sign each attempt again, and keep your clock in sync.- 401
invalid_key The key id is unknown or revoked, or the app is archived.
- 401
invalid_signature The signature does not match. Sign the exact bytes you send.
- 429
too_many_attempts Too many requests with this key in the last minute. Wait
Retry-Afterseconds.
TS=$(date +%s)
SIG=$(printf '%s.' "$TS" | openssl dgst -sha256 -hmac "$FG_SECRET" | sed 's/^.*= //')
curl "https://api.flowgroup.se/v1/affiliates/config" \
-H "x-fg-key: $FG_KEY_ID" \
-H "x-fg-timestamp: $TS" \
-H "x-fg-signature: $SIG"import { createFlowGrowth } from '@flow/growth-sdk';
const fg = createFlowGrowth({
baseUrl: 'https://api.flowgroup.se',
keyId: process.env.FG_KEY_ID!, // fg_test_… in the sandbox
secret: process.env.FG_SECRET!,
});
const res = await fg.getAffiliateConfig('SE');
if (res.ok && res.data.mode === 'live') {
// stop crediting referral rewards yourself in this market
}{
"mode": "live",
"programId": "0c9e6f3a-1b2d-4c5e-8f7a-9b0c1d2e3f4a",
"campaignId": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"currency": "USD",
"rules": [
{
"id": "9f8e7d6c-5b4a-4392-8170-6f5e4d3c2b1a",
"name": "Friend makes a first purchase",
"triggerEvent": "purchase.completed",
"model": "fixed",
"amount": 200,
"percentBps": null,
"maxAmount": null,
"currency": "USD",
"beneficiary": "partner",
"recurring": false,
"holdDays": 14,
"qualification": {
"withinDays": 30
}
}
],
"updatedAt": "2026-11-01T08:00:00.000Z"
}Import your users’ existing codes
/v1/affiliates/codesHands over the invite codes your users already share in your own scheme, so each code stays theirs and keeps working here. Safe to repeat. Codes are real identities, so a sandbox key changes nothing and answers sandbox: true.
AuthSigned request with an app key, fg_live_… or fg_test_…
Request body
codesarray of objectsrequiredChild attributes
appUserIdstringrequiredYour pseudonymous id for the user. Never an email address or phone number.
codestringrequiredThe code the user shares in your app today.
Returns · 200
importedintegerrequiredexistingintegerrequiredAlready theirs.
conflictsarray of objectsrequiredChild attributes
appUserIdstringrequiredcodestringrequiredreasonstringrequiredcode_taken: another user holds it here. invalid_code: not 3 to 24 letters and digits.
sandboxbooleanSent with a sandbox key: nothing was imported.
Errors
- 400
validation_error The request does not match the schema.
detailssays which fields failed and why.- 401
missing_signature One of
x-fg-key,x-fg-timestampandx-fg-signatureis missing.- 401
invalid_timestamp x-fg-timestampis not Unix time in seconds.- 401
stale_timestamp x-fg-timestampis more than 300 seconds from the server clock. Sign each attempt again, and keep your clock in sync.- 401
invalid_key The key id is unknown or revoked, or the app is archived.
- 401
invalid_signature The signature does not match. Sign the exact bytes you send.
- 404
not_found Your app has no active friend-referral program.
- 429
too_many_attempts Too many requests with this key in the last minute. Wait
Retry-Afterseconds.
BODY='{"codes":[{"appUserId":"41","code":"K7QXA"}]}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$FG_SECRET" | sed 's/^.*= //')
curl -X POST "https://api.flowgroup.se/v1/affiliates/codes" \
-H "content-type: application/json" \
-H "x-fg-key: $FG_KEY_ID" \
-H "x-fg-timestamp: $TS" \
-H "x-fg-signature: $SIG" \
--data-raw "$BODY"import { createFlowGrowth } from '@flow/growth-sdk';
const fg = createFlowGrowth({
baseUrl: 'https://api.flowgroup.se',
keyId: process.env.FG_KEY_ID!, // fg_test_… in the sandbox
secret: process.env.FG_SECRET!,
});
const res = await fg.importAffiliateCodes([
{ appUserId: '41', code: 'K7QXA' },
{ appUserId: '42', code: 'M2NB9' },
]);{
"imported": 1,
"existing": 0,
"conflicts": [
{
"appUserId": "42",
"code": "k7qxa",
"reason": "code_taken"
}
]
}