API reference · App API
Attribution
Ask whether a code or click your app caught can be attributed, before the user signs up, and get the attribution key that carries the referral from then on.
Ask whether a referral can be attributed
/v1/attributionsAsk as soon as your app has caught something: the link it was opened with (url), your deep-link SDK’s referring params as they are (params), the Play install referrer, or a typed code. It works before sign-up, so you can show “Invited by …” on the sign-up screen.
A yes carries an attributionKey. Keep it, and once you know the user is new, confirm with user.registered carrying the key and the new appUserId: that binds the key and attributes the user. A user who is not new never gets the key sent; it expires unused, or you can decline it.
Send an installId and asking again from the same install answers with the same key. Without one, every yes is a new key, unless appUserId names a user who already has one for this code.
AuthSigned request with an app key, fg_live_… or fg_test_…
Request body
codestringfg_ref, the code in a /a/<code> link, or a code the user typed into your own referral field that you want checked against Flow Affiliate. Case and surrounding spaces do not matter.
clickIdstringfg_click from the link, Branch params or the Play install referrer. Used when there is no code, or the code is not one of yours.
urlstringThe link the app was opened with, as it arrived. fg_ref and fg_click are read from it, and the code of a Flow Affiliate link (/a/<code> on its tracking host). Your app’s own referral links are ignored.
paramsobjectThe referring params your deep-link SDK hands you (Branch, AppsFlyer, Adjust), forwarded as they are. Only Flow Affiliate’s own data is read: fg_ref and fg_click from its own keys, and from any value that is a link carrying them or a Flow Affiliate link, such as ~referring_link. Keys of your own referral scheme, such as code, are never read. Branch’s +match_guaranteed sets confidence. Nothing else in it is kept.
installIdstringYour own random id for this install. Asking again with the same id answers with the key it already has, instead of making a new one.
sourcestringHow the code reached the app. Worked out from what you send when left out: branch for Branch params, link for a link or click id, code_entry for a code alone. app_clip for a click handed over by your App Clip, install_match for a click that POST /v1/installs/match matched the install to (always recorded with confidence: probable). Any other value is recorded as other, never refused.
One of link branch play_referrer code_entry app_clip install_match other
platformstringThe platform the app runs on. Any other value is ignored.
One of ios android
confidencestringHow sure your link provider is of the match, e.g. deterministic for Branch +match_guaranteed. Recorded, never decides anything.
One of deterministic probable
appUserIdstringOnly when the user already has an account: the key is then bound to them at once. Before sign-up, leave it out.
Returns · 200 · one of
Option 1
attributablebooleanrequiredattributionKeystringrequiredKeep it with the install or the sign-up form. Send it on user.registered if you decide the user is new.
statusstringrequiredpending: waiting for user.registered. registered: a registration arrived for the key’s user. declined: you closed it. expired: the window closed before the key was registered.
One of pending registered declined expired
partnerobjectrequiredChild attributes
displayNamestringrequiredWho referred the user, as they are shown.
programobjectrequiredChild attributes
idstringrequirednamestringrequiredinviteeBonusobjectrequiredThe welcome gift the link carries for the invited user. Null when it carries none.
Child attributes
amountintegerrequiredMinor units.
currencystringrequiredqualifyingEventsarray of stringsrequiredEvent types the user must complete; the bonus follows the first.
withinDaysintegerrequiredexpiresAtstringrequiredThe user must register before this to get it.
expiresAtstringrequiredA key not registered by then is no longer accepted: the program’s attribution window.
Option 2
attributablebooleanrequiredreasonstringrequiredno_affiliate: nothing you sent names a code or a click, e.g. the params of an install that came from no link.unknown_code: no code like this belongs to your app.unknown_click: no code was sent, and the click id is not a click on one of your links.code_inactive: the code was switched off or has expired, or its referrer can no longer be paid.program_closed: the program the code belongs to is not running.already_attributed: theappUserIdyou sent is already attributed.
One of no_affiliate unknown_code unknown_click code_inactive program_closed already_attributed
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='{"params":{"+clicked_branch_link":true,"+match_guaranteed":false,"fg_ref":"k7qxa2mp","fg_click":"1d4f9c5e-7a8b-4c2d-9e1f-0a1b2c3d4e5f","~referring_link":"https://example.app.link/AbC123"},"installId":"5b1d0c9e-2f4a-4b8c-9d7e-6f5a4b3c2d1e","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/attributions" \
-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!,
});
// what the app caught, as it caught it: Branch params, the link it opened with, or a typed code
const res = await fg.createAttribution({ params: branchParams, installId, platform: 'ios' });
if (res.ok && res.data.attributable) {
saveForInstall(installId, res.data.attributionKey); // send it on user.registered once the user is new
showInvitedBy(res.data.partner.displayName);
} else if (res.ok && res.data.reason === 'unknown_code') {
// not a Flow Affiliate code: your own referral scheme may know it
}{
"attributable": true,
"attributionKey": "fga_k7qxa2mpw4ht9bvc3rnd8ezj",
"status": "pending",
"partner": {
"displayName": "Anna Berg"
},
"program": {
"id": "0c9e6f3a-1b2d-4c5e-8f7a-9b0c1d2e3f4a",
"name": "Invite friends"
},
"inviteeBonus": null,
"expiresAt": "2026-12-09T09:30:00Z"
}Get an attribution key
/v1/attributions/{key}What became of a key: who it belongs to, whether it was registered or declined, and the conversion it made.
AuthSigned request with an app key, fg_live_… or fg_test_…
Path parameters
keystringrequiredThe attributionKey, fga_….
Returns · 200
attributionKeystringrequiredstatusstringrequiredpending: waiting for user.registered. registered: a registration arrived for the key’s user. declined: you closed it. expired: the window closed before the key was registered.
One of pending registered declined expired
partnerobjectrequiredChild attributes
displayNamestringrequiredWho referred the user, as they are shown.
programobjectrequiredChild attributes
idstringrequirednamestringrequiredappUserIdstringrequiredThe user the key is bound to; null until it is registered.
createdAtstringrequiredexpiresAtstringrequireddeclineReasonstringrequiredWhy you declined it; null unless status is declined.
One of existing_user duplicate suspected_fraud other
conversionobjectrequiredThe conversion this key made, once the registration has been processed. Null when there is none, or the user was attributed to another referral.
Child attributes
statusstringrequiredOne of qualification_pending qualified rejected reversed
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 key like this belongs to your app in this environment.
- 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/attributions/key" \
-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.getAttribution('fga_k7qxa2mpw4ht9bvc3rnd8ezj');
if (res.ok) console.log(res.data.status, res.data.conversion);{
"attributionKey": "fga_k7qxa2mpw4ht9bvc3rnd8ezj",
"status": "registered",
"partner": {
"displayName": "Anna Berg"
},
"program": {
"id": "0c9e6f3a-1b2d-4c5e-8f7a-9b0c1d2e3f4a",
"name": "Invite friends"
},
"appUserId": "u_8F2K",
"createdAt": "2026-11-09T09:28:00Z",
"expiresAt": "2026-12-09T09:28:00Z",
"declineReason": null,
"conversion": {
"status": "qualification_pending"
}
}Decline an attribution key
/v1/attributions/{key}/declineOptional. When you have decided the user behind a key is not new, you can simply never send the key. Declining it says why: the key is closed at once, and the publisher sees your reason instead of a key that expired without a word.
A declined key is refused on every event (key_declined). Declining again changes nothing. A key already registered cannot be declined: undo what it earned with a reversal.
AuthSigned request with an app key, fg_live_… or fg_test_…
Path parameters
keystringrequiredThe attributionKey, fga_….
Request body
reasonstringrequiredexisting_user: they already had an account. duplicate: the same person or device signed up before. suspected_fraud: you will not accept this sign-up. other: anything else.
One of existing_user duplicate suspected_fraud other
notestringFor our records, never shown to the publisher. Nothing that identifies the person.
Returns · 200
attributionKeystringrequiredstatusstringrequiredpending: waiting for user.registered. registered: a registration arrived for the key’s user. declined: you closed it. expired: the window closed before the key was registered.
One of pending registered declined expired
partnerobjectrequiredChild attributes
displayNamestringrequiredWho referred the user, as they are shown.
programobjectrequiredChild attributes
idstringrequirednamestringrequiredappUserIdstringrequiredThe user the key is bound to; null until it is registered.
createdAtstringrequiredexpiresAtstringrequireddeclineReasonstringrequiredWhy you declined it; null unless status is declined.
One of existing_user duplicate suspected_fraud other
conversionobjectrequiredThe conversion this key made, once the registration has been processed. Null when there is none, or the user was attributed to another referral.
Child attributes
statusstringrequiredOne of qualification_pending qualified rejected reversed
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 key like this belongs to your app in this environment.
- 409
key_registered The key was already registered.
- 429
too_many_attempts Too many requests with this key in the last minute. Wait
Retry-Afterseconds.
BODY='{"reason":"existing_user"}'
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/attributions/key/decline" \
-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!,
});
// optional: the user was not new, and the publisher should see why
const res = await fg.declineAttribution('fga_k7qxa2mpw4ht9bvc3rnd8ezj', { reason: 'existing_user' });{
"attributionKey": "fga_k7qxa2mpw4ht9bvc3rnd8ezj",
"status": "registered",
"partner": {
"displayName": "Anna Berg"
},
"program": {
"id": "0c9e6f3a-1b2d-4c5e-8f7a-9b0c1d2e3f4a",
"name": "Invite friends"
},
"appUserId": "u_8F2K",
"createdAt": "2026-11-09T09:28:00Z",
"expiresAt": "2026-12-09T09:28:00Z",
"declineReason": null,
"conversion": {
"status": "qualification_pending"
}
}