# Flinku Integration Guide for AI Assistants Flinku is a mobile deep linking SaaS platform, the modern replacement for Firebase Dynamic Links (shut down August 2025). It provides deferred deep linking, automatic referral attribution (SDK 0.6.0), short URLs, QR codes, analytics, and SDKs for Flutter, iOS, Android, React Native, Capacitor, and Unity. Use this file to integrate Flinku into any app. Everything you need is here. No other files required. --- ## How Flinku works 1. A link is created (via dashboard or API), e.g. `https://yourapp.flku.dev/summer-sale` 2. User taps the link on mobile 3. Flinku shows a Deepview page with your app icon and an "Open App" button 4. If app is installed → opens directly to the right screen via Universal Links / App Links 5. If app is not installed → redirects to App Store / Play Store 6. After install, on first launch, the SDK calls `match()` → Flinku resolves the click via fingerprint and clipboard on all SDKs at stable release. Play Install Referrer (deterministic, Android Play Store installs) is available in the native Android SDK and in the Flutter prerelease `0.8.0-beta.1` only — stable Flutter (`0.7.2`, what `flutter pub add flinku_sdk` installs) does not include it; opt in with an explicit pin (`flinku_sdk: 0.8.0-beta.1` in pubspec.yaml, not a caret constraint) and a full app rebuild. React Native, Capacitor, and Unity match Android installs via fingerprint and clipboard only. The SDK returns the original deep link so the app navigates to the right screen (deferred deep linking) ### Deferred match consumption (server-side, all SDKs, July 21 2026) When `match()` successfully returns a deferred link, the server marks it **consumed** on first return. A **5-minute grace window** allows the same match to be returned again if the app restarts mid flow. **After the grace window**, the server will not return that match again. **Fingerprint attribution** remains available for **24 hours** from click to first app open. SDK `reset()` clears client cache only; it does not undo server consumption. No SDK update required — this is server-side behavior. ### match() vs resolving a short link `match()` is for **deferred deep linking** (install attribution after a pre-install click). When an **already installed** user taps a short URL and Universal Links / App Links open the app with e.g. `https://yourapp.flku.dev/abc123`, call the public resolve endpoint instead — do not use `match()` for that. ``` GET https://flku.dev/api/links/resolve/:slug?subdomain=yourapp ``` No authentication required. Returns `{ slug, deepLink, params, title }` as JSON without redirecting. 400: `{ "error": "slug and subdomain are required" }` · 404: `{ "error": "Link not found" }` Worked example: ```dart final uri = Uri.parse(incomingUrl); // https://yourapp.flku.dev/abc123 final subdomain = uri.host.split('.').first; final slug = uri.pathSegments.first; final res = await http.get(Uri.parse( 'https://flku.dev/api/links/resolve/$slug?subdomain=$subdomain', )); final data = jsonDecode(res.body); // navigate with data['deepLink'] and data['params'] ``` --- ## SDK integrations React Native and Capacitor use a **class-based, instance-only API**. Create one `Flinku` instance at app startup with `new Flinku({ userId, baseUrl, apiKey })` and call every method on that instance. There is no `configure()` method and no exported singleton. ⚠️ Do not wrap createLinkInstant in an async function. It is synchronous and returns immediately. Wrapping it in async/await will add unnecessary delay and defeat the purpose. ### Flutter SDK #### Install ```yaml dependencies: flinku_sdk: ^0.7.2 ``` #### Configure (main.dart) ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); Flinku.configure( baseUrl: 'https://yourapp.flku.dev', apiKey: 'flk_pk_...', // publishable key; required for referrals. Never use flk_live_ in the app. ); runApp(MyApp()); } ``` #### Match deferred deep link on every cold start ```dart final link = await Flinku.match(); if (link != null && link.deepLink != null) { navigateToDeepLink(link.deepLink!, link.params); await Flinku.reset(); // always call after handling, prevents re-firing on next launch } else { navigateToHome(); // no match: fresh install or direct open } ``` #### Access params ```dart final link = await Flinku.match(); if (link != null) { final ref = link.params?['ref']; // e.g. 'instagram' final promo = link.params?['promo']; // e.g. 'SAVE20' } ``` #### Native setup: iOS (Required) Xcode → Target → Signing & Capabilities → + Capability → Associated Domains: applinks:yourapp.flku.dev #### Native setup: Android (Required) Add to `AndroidManifest.xml`: ```xml ``` #### Reset (testing only) ```dart await Flinku.reset(); ``` Clears the cached match result only. Does not clear the stored user id or pending referral attribution. This changed in 0.6.0. #### resetAll (testing only) ```dart await Flinku.resetAll(); ``` Clears everything `reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. createLinkInstant(options): returns shortUrl instantly, saves to server in background --- ### iOS SDK (Swift) #### Install via Swift Package Manager Xcode → File → Add Package Dependencies: https://github.com/flinku-dev/ios-sdk Exact Version: 0.7.1 Requires iOS 14+, Swift 5.9+ #### Configure ```swift import FlinkuSDK @main struct MyApp: App { init() { Flinku.configure( baseUrl: "https://yourapp.flku.dev", apiKey: "flk_pk_..." // publishable key; required for referrals. Never use flk_live_ in the app. ) } var body: some Scene { WindowGroup { ContentView() } } } ``` configure(baseUrl:apiKey:readClipboard:): set readClipboard: false to skip clipboard read and suppress iOS paste dialog when Universal Links handle attribution. Default is true. #### Match deferred deep link on every cold start ```swift struct SplashView: View { var body: some View { ProgressView() .task { let link = await Flinku.match() if link.matched { NavigationState.shared.deepLink = link.deepLink } else { NavigationState.shared.navigateHome() } } } } ``` #### Access params ```swift let link = await Flinku.match() if link.matched, let params = link.params { let ref = params["ref"] as? String // e.g. "instagram" let promo = params["promo"] as? String // e.g. "SAVE20" } ``` #### Associated Domains (Required) Xcode → Target → Signing & Capabilities → + Capability → Associated Domains: applinks:yourapp.flku.dev #### Reset (testing only) ```swift Flinku.reset() ``` Clears the cached match result only (removes the two match keys). Does not clear the stored user id or pending referral attribution. This changed in 0.6.0. #### resetAll (testing only) ```swift Flinku.resetAll() ``` Clears everything `reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. createLinkInstant(options): returns shortUrl instantly, saves to server in background --- ### Android SDK (Kotlin) #### Install via JitPack Root `build.gradle`: ```gradle allprojects { repositories { maven { url 'https://jitpack.io' } } } ``` App `build.gradle`: ```gradle dependencies { implementation 'com.github.flinku-dev:android-sdk:0.7.1' } ``` #### Configure (Application class) ```kotlin class MyApplication : Application() { override fun onCreate() { super.onCreate() Flinku.configure( this, baseUrl = "https://yourapp.flku.dev", apiKey = "flk_pk_..." // publishable key; required for referrals. Never use flk_live_ in the app. ) } } ``` #### Match deferred deep link on every cold start ```kotlin class SplashActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { val link = Flinku.match(this@SplashActivity) if (link.matched) { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.deepLink))) finish() } else { startActivity(Intent(this@SplashActivity, MainActivity::class.java)) finish() } } } } ``` #### Access params ```kotlin val link = Flinku.match(this@MyActivity) if (link.matched) { val ref = link.params?.get("ref") as? String // e.g. "instagram" val promo = link.params?.get("promo") as? String // e.g. "SAVE20" } ``` #### AndroidManifest.xml (Required) ```xml ``` #### Reset (testing only) ```kotlin Flinku.reset(context) ``` Clears the cached match result only. Does not clear the stored user id or pending referral attribution. This changed in 0.6.0. #### resetAll (testing only) ```kotlin Flinku.resetAll(context) ``` Clears everything `reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. createLinkInstant(options): returns shortUrl instantly, saves to server in background --- ### React Native SDK #### Install ```bash npm install flinku-react-native@^0.7.4 @react-native-async-storage/async-storage ``` #### Initialize and match on every cold start ```typescript import { Flinku } from 'flinku-react-native'; // userId = any stable unique identifier for the current user // (Firebase UID, Supabase UID, your own user ID, or a device UUID for anonymous users) const flinku = new Flinku({ userId: 'current-user-uid', baseUrl: 'https://yourapp.flku.dev', apiKey: 'flk_pk_...', // publishable key; required for referrals. Never use flk_live_ in the app. }); const link = await flinku.match(); if (link) { console.log(link.deepLink); // 'yourapp://screen' console.log(link.params); // { ref: 'instagram', promo: 'SAVE20' } // navigate to the right screen } else { // no match: navigate to home } ``` #### Native setup (Required) React Native apps still need native configuration: - iOS: Add `applinks:yourapp.flku.dev` to Associated Domains (see iOS section above) - Android: Add intent-filter to AndroidManifest.xml (see Android section above) #### Reset (testing only) ```typescript await flinku.reset(); ``` Clears the cached match result only. Does not clear the stored user id or pending referral attribution. This changed in 0.6.0. #### resetAll (testing only) ```typescript await flinku.resetAll(); ``` Clears everything `reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. createLinkInstant(options): returns shortUrl instantly, saves to server in background --- ### Capacitor / Ionic SDK #### Install ```bash npm install flinku-capacitor@^0.7.4 ``` #### Initialize and match on every cold start ```typescript import { Flinku } from 'flinku-capacitor'; // or: import Flinku from 'flinku-capacitor'; const flinku = new Flinku({ userId: 'current-user-uid', // any stable unique identifier for the current user baseUrl: 'https://yourapp.flku.dev', apiKey: 'flk_pk_...', // publishable key; required for referrals. Never use flk_live_ in the app. }); const link = await flinku.match(); if (link) { console.log(link.deepLink); console.log(link.params); } else { // no match: navigate to home } ``` #### Native setup (Required) - iOS: Add `applinks:yourapp.flku.dev` to Associated Domains (see iOS section above) - Android: Add intent-filter to AndroidManifest.xml (see Android section above) #### Reset (testing only) ```typescript await flinku.reset(); ``` Clears the cached match result only. Does not clear the stored user id or pending referral attribution. This changed in 0.6.0. #### resetAll (testing only) ```typescript await flinku.resetAll(); ``` Clears everything `reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. createLinkInstant(options): returns shortUrl instantly, saves to server in background --- ### Unity SDK #### Install via Unity Package Manager Window → Package Manager → + → Add package from git URL: https://github.com/flinku-dev/unity-sdk.git Use tag / version 0.7.1. Requires Newtonsoft Json, resolved automatically from package.json. #### Configure and match on every cold start ```csharp using Flinku; void Start() { var flinku = FlinkuSDK.Initialize(new FlinkuConfig { UserId = "current-user-uid", // any stable unique identifier for the current user BaseUrl = "https://yourapp.flku.dev", ApiKey = "flk_pk_..." // publishable key; required for referrals. Never use flk_live_ in the app. }); flinku.Match(link => { if (link != null) { Debug.Log($"Deep link: {link.DeepLink}"); // link.Params: Dictionary // navigate to the right scene } else { // no match: load default scene } }); } ``` #### Reset (testing only) ```csharp FlinkuSDK.Instance.Reset(); ``` Clears the cached match result only. Does not clear the stored user id or pending referral attribution. This changed in 0.6.0. #### resetAll (testing only) ```csharp FlinkuSDK.Instance.ResetAll(); ``` Clears everything `Reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. createLinkInstant(options): returns shortUrl instantly, saves to server in background --- ## Referral attribution (SDK 0.6.0) Flinku attributes installs and qualified actions to users who shared invite links, even when the App Store or Play Store breaks the click path. Do not build DIY attribution by manually reading a `ref` / `referrerId` param after `match()` and posting to your own backend for Flinku's referral system. Use the SDK methods below. Flow: 1. Create an invite link with params `referrerId` (required) and optional `referrerLabel`. 2. Configure the SDK with `baseUrl` and publishable `apiKey` (`flk_pk_...`). Without the publishable key, referral tracking silently does nothing. 3. After the new user signs up or logs in, call `setUserId`. The SDK attributes the referral automatically. 4. When they do the rewarded action, call `qualifyReferral(event)`. Never embed your secret key (`flk_live_`) in the app. Never call `POST /api/referrals/track` by hand from the mobile client. ### Flutter ```dart Flinku.configure( baseUrl: 'https://yourapp.flku.dev', apiKey: 'flk_pk_...', ); // Create invite (prefer your backend with flk_live_; publishable key is OK for in-app createLink) final link = await Flinku.createLink(FlinkuLinkOptions( title: 'Invite from Alice', deepLink: 'myapp://home', params: { 'referrerId': 'user_123', 'referrerLabel': 'Alice', }, )); // After signup or login Flinku.setUserId(user.id); // When they do the rewarded action Flinku.qualifyReferral('purchase'); ``` ### iOS ```swift Flinku.configure( baseUrl: "https://yourapp.flku.dev", apiKey: "flk_pk_..." ) Flinku.setUserId(user.id) Flinku.qualifyReferral("purchase") ``` ### Android ```kotlin Flinku.configure( context, baseUrl = "https://yourapp.flku.dev", apiKey = "flk_pk_..." ) Flinku.setUserId(context, user.id) Flinku.qualifyReferral(context, "purchase") ``` ### React Native ```typescript const flinku = new Flinku({ userId: 'anonymous', baseUrl: 'https://yourapp.flku.dev', apiKey: 'flk_pk_...', }); flinku.setUserId(user.id); flinku.qualifyReferral('purchase'); ``` ### Capacitor ```typescript const flinku = new Flinku({ userId: 'anonymous', baseUrl: 'https://yourapp.flku.dev', apiKey: 'flk_pk_...', }); flinku.setUserId(user.id); flinku.qualifyReferral('purchase'); ``` ### Unity ```csharp var sdk = FlinkuSDK.Initialize(new FlinkuConfig { BaseUrl = "https://yourapp.flku.dev", ApiKey = "flk_pk_..." }); sdk.SetUserId(user.Id); sdk.QualifyReferral("purchase"); ``` `reset()` clears the match cache only. It does not clear the stored user id or pending referral attribution. This changed in 0.6.0. Correct forms: Flutter `await Flinku.reset()`, iOS `Flinku.reset()`, Android `Flinku.reset(context)`, React Native `await flinku.reset()`, Capacitor `await flinku.reset()`, Unity `FlinkuSDK.Instance.Reset()`. `resetAll()` clears everything `reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. Correct forms: Flutter `await Flinku.resetAll()`, iOS `Flinku.resetAll()`, Android `Flinku.resetAll(context)`, React Native `await flinku.resetAll()`, Capacitor `await flinku.resetAll()`, Unity `FlinkuSDK.Instance.ResetAll()`. Docs: https://docs.flinku.dev/docs/referral-system/ --- ## In-app link creation (product sharing, referral links, etc.) Many apps create links automatically when a user taps Share, generates a referral link, or shares a product. This is the correct architecture: User taps "Share" in app → App calls YOUR backend (your server, not Flinku directly) → YOUR backend calls POST https://flku.dev/api/links with the API key → Your backend returns the shortUrl to the app → App shows or shares the shortUrl NEVER put the API key in your mobile app. It can be extracted. Always proxy through your backend. ### Backend example (Node.js) ```javascript app.post('/create-share-link', async (req, res) => { const { productId, userId } = req.body; const response = await fetch('https://flku.dev/api/links', { method: 'POST', headers: { 'Authorization': 'Bearer flk_live_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ projectId: 'your_project_id', title: `Product ${productId}`, deepLink: `yourapp://product/${productId}`, params: { ref: userId, source: 'share' }, }), }); const { shortUrl } = await response.json(); res.json({ shortUrl }); }); ``` ### App calls your backend (Flutter example) ```dart final response = await http.post( Uri.parse('https://yourbackend.com/create-share-link'), body: jsonEncode({ 'productId': '123', 'userId': currentUser.id }), ); final shortUrl = jsonDecode(response.body)['shortUrl']; Share.share(shortUrl); ``` ### Batch link creation (high volume, e.g. referral links for all users) ```bash POST https://flku.dev/api/links/batch Authorization: Bearer flk_live_your_api_key Content-Type: application/json [ { "projectId": "...", "title": "Referral, Alice", "deepLink": "yourapp://home", "params": { "referrerId": "alice", "referrerLabel": "Alice" } }, { "projectId": "...", "title": "Referral, Bob", "deepLink": "yourapp://home", "params": { "referrerId": "bob", "referrerLabel": "Bob" } } ] ``` Max 100 links per batch request. --- ## API reference ### Authentication Server-side secret key: Authorization: Bearer flk_live_your_api_key Publishable key for mobile SDKs (referrals): flk_pk_... Get keys: Flinku dashboard → Project → Settings → API Keys. NEVER put `flk_live_` secret keys in mobile app code. `flk_pk_` publishable keys are required in the app for referral tracking. ### GET /api/links/resolve/:slug?subdomain=... — Resolve short link to JSON (no auth) Public endpoint for an **already installed** app that received a Flinku short URL over Universal Links or App Links. Returns slug, deepLink, params, and title as JSON **without redirecting**. **No authentication required.** Do **not** confuse with `match()` / `POST /api/match`: - `match()` = deferred deep linking (install attribution after a pre-install click) - `GET /api/links/resolve/...` = resolve a short URL the installed app already received ``` GET https://flku.dev/api/links/resolve/abc123?subdomain=yourapp ``` **200:** ```json { "slug": "abc123", "deepLink": "yourapp://product/42", "params": { "ref": "instagram", "promo": "SAVE20" }, "title": "Spring Sale" } ``` **400** (missing slug or subdomain): `{ "error": "slug and subdomain are required" }` **404** (unknown link): `{ "error": "Link not found" }` Worked example (Flutter): ```dart // Incoming App Link: https://yourapp.flku.dev/abc123 final uri = Uri.parse(incomingUrl); final subdomain = uri.host.split('.').first; final slug = uri.pathSegments.first; final res = await http.get(Uri.parse( 'https://flku.dev/api/links/resolve/$slug?subdomain=$subdomain', )); final data = jsonDecode(res.body); // navigate with data['deepLink'] and data['params'] ``` ### POST /api/links: Create a single link **Required fields:** | Field | Type | Description | |---|---|---| | projectId | string | Project ID from the dashboard | | title | string | Link title, used to auto-generate the slug | | deepLink | string | URI scheme deep link e.g. `yourapp://screen/123` | **Optional fields:** | Field | Type | Description | |---|---|---| | params | object | Key-value pairs appended to the deep link URI on match | | slug | string | Custom slug, auto-generated if omitted | | expiresAt | string | ISO datetime, link deactivates after this | | scheduledAt | string | ISO datetime, link shows "Coming Soon" until this time | | maxClicks | number | Deactivate link after this many clicks | | password | string | Require a password to access the link | | ogTitle | string | Social preview title (Open Graph) | | ogDescription | string | Social preview description | | ogImageUrl | string | Social preview image (https only) | | desktopUrl | string | Redirect URL for desktop users instead of Deepview | | appClipUrl | string | iOS App Clip experience URL (https only) | | instantAppUrl | string | Android Instant App URL (https only) | | tags | string[] | Tags for organization and filtering | | geoRouting | object[] | `[{ "country": "US", "url": "https://..." }]` (up to 10 rules | | fallbackChain | object[] | Smart fallback URLs checked in order (HEAD check, first 200 wins) | | influencerId | string | Affiliate/influencer ID for tracking | **Example request:** ```json { "projectId": "your_project_id", "title": "Summer sale promo", "deepLink": "yourapp://promo/summer", "params": { "ref": "email", "promo": "SAVE20" }, "ogTitle": "Summer Sale, 20% off", "ogDescription": "Use code SAVE20 at checkout", "ogImageUrl": "https://yourapp.com/promo.jpg", "desktopUrl": "https://yourapp.com/promo", "expiresAt": "2025-09-01T00:00:00Z", "maxClicks": 1000, "tags": ["campaign", "email"] } ``` **Response:** ```json { "shortUrl": "https://yourapp.flku.dev/summer-sale-promo", "slug": "summer-sale-promo" } ``` If the project has an active custom domain, `shortUrl` will use that domain instead (e.g. `https://links.yourapp.com/summer-sale-promo`). --- ## Dynamic URL variables Use `#{key}` placeholders in `deepLink`, `desktopUrl`, or fallback URLs. Flinku replaces them at redirect time. **Built-in variables:** | Variable | Value | |---|---| | `#{slug}` | The link's slug | | `#{platform}` | `ios`, `android`, or `web` | | `#{country}` | Two-letter country code e.g. `US`, `GB` | | `#{timestamp}` | Unix timestamp at redirect time | **Query param pass-through:** Any query param on the short URL is also available. Example: https://yourapp.flku.dev/promo?influencer=john `#{influencer}` → `john` in the deep link. **Example:** ```json { "deepLink": "yourapp://promo?ref=#{influencer}&platform=#{platform}&country=#{country}" } ``` Unknown `#{key}` placeholders are left as-is (not replaced). --- ## Journeys web banner Add a smart mobile banner to your marketing website. It promotes your app and deep links into the store or app on mobile devices. Desktop users see nothing. ### Setup 1. Go to Flinku dashboard → Project → Journeys 2. Copy the embed snippet 3. Paste into your website's `` or before `` The snippet loads a public script. No API key in the browser: GET /api/projects/:projectId/journey.js The banner detects mobile vs desktop automatically and is dismissible by the user. --- ## Deepview page When a user opens a Flinku link on mobile, they land on a Deepview page, a branded interstitial that: - Shows your app icon (auto-fetched from iTunes API) - Shows your app name - Shows an "Open App" or "Download on App Store / Google Play" button **Customization (dashboard → Project → Deepview):** - Custom colors and layout - Custom branding - Remove "Powered by Flinku" (Studio plan only) --- ## Well-known endpoints: nothing to configure Flinku automatically serves the files iOS and Android need to verify Universal Links and App Links. You do not need to host these yourself. | File | URL | Purpose | |---|---|---| | Apple App Site Association | `https://yourapp.flku.dev/.well-known/apple-app-site-association` | iOS Universal Links verification | | Android Asset Links | `https://yourapp.flku.dev/.well-known/assetlinks.json` | Android App Links verification | These are also served automatically for custom domains when active. --- ## Custom domains (Indie and Studio plans) Indie and Studio users can use their own domain (e.g. `links.yourapp.com`) instead of `yourapp.flku.dev`. - Set up in dashboard → Project → Custom Domain - Point a CNAME DNS record to Flinku. SSL is provisioned automatically - Once active, all new short URLs use your custom domain - AASA and assetlinks.json are served automatically for the custom domain too - SDK `baseUrl` still uses your `flku.dev` subdomain. The SDK resolves the API URL internally. Do not change `baseUrl` when adding a custom domain. --- ## Rate limits | Limit | Value | Scope | |---|---|---| | Global API | 100 req/min | Per IP | | match() endpoint | 10 req/min | Per IP | | Link redirects | 60 req/min | Per IP | | Project redirects | 5,000 req/hour | Per project subdomain | | API key writes (link creation) | 1,000 req/hour | Per API key | If you need higher limits for bulk link creation, batch your requests using `POST /api/links/bulk` (max 100 per request) or `POST /api/links/bulk-csv` (max 400 rows / 1MB per upload). --- ## Error codes | Code / Message | HTTP | Meaning | |---|---|---| | `PLAN_LIMIT_REACHED` | 403 | Hit plan link or click limit. Upgrade required | | `Slug already in use` | 409 | Custom slug taken in this project. Use a different one | | `title is required` | 400 | Missing or empty title field | | `deepLink is required` | 400 | Missing or empty deepLink field | | `projectId is required` | 400 | Missing projectId field | | `API key rate limit exceeded` | 429 | Exceeded 1,000 link creations/hour for this API key | | `Too many requests` | 429 | Global rate limit hit | --- ## Testing deep links - Always test on a real device. Fingerprint matching uses IP and User-Agent, which simulators don't replicate accurately - To test deferred deep linking: uninstall app → tap link → install app → launch → `match()` should return the link - Fingerprint attribution expires **24 hours** after the original click - **Android (ADB vs Play Store):** Installing over ADB (`npm run android`, Android Studio Run, or `adb install`) exercises the **fingerprint matching path only**. Play Install Referrer is only available when the app is installed from the **Google Play Store**, and only in the **native Android SDK** and the **Flutter prerelease `0.8.0-beta.1`** — stable Flutter (`0.7.2`) does not include it; pin `flinku_sdk: 0.8.0-beta.1` explicitly in pubspec.yaml (a caret will not resolve to a prerelease) and rebuild the app. React Native, Capacitor, and Unity do not read Install Referrer. To verify the referrer path end-to-end, distribute through **Google Play Internal Testing** (or another Play track) with a supported SDK, install from the Play Store, then run your test flow. - Deferred match consumption (server-side, all SDKs): first successful `match()` return consumes the match; a **5-minute grace window** allows the same match again on restart; after that window the server will not return it again - To reset the match cache during testing: - Flutter: `await Flinku.reset()` - iOS: `Flinku.reset()` - Android: `Flinku.reset(context)` - React Native: `await flinku.reset()` - Capacitor: `await flinku.reset()` - Unity: `FlinkuSDK.Instance.Reset()` Clears the cached match result only. Does not clear stored user id or pending referral attribution (changed in 0.6.0). - If referral or match state is still wrong during testing, call `resetAll()` to wipe all Flinku local state. Clears everything `reset()` clears, plus the stored user id, referral project id, all pending referral records, and any tracked-once markers. **Testing only — do not call in production.** Calling it in production destroys real referral attribution. Added in 0.7.0. - Flutter: `await Flinku.resetAll()` - iOS: `Flinku.resetAll()` - Android: `Flinku.resetAll(context)` - React Native: `await flinku.resetAll()` - Capacitor: `await flinku.resetAll()` - Unity: `FlinkuSDK.Instance.ResetAll()` - Free tools at https://flinku.dev/tools: - Deep Link Tester: test your links end to end - AASA Validator: verify your apple-app-site-association is correct - Android Assets Validator: verify your assetlinks.json --- ## Universal Links not opening the app on iOS (Flutter) These are Flutter and iOS platform issues, not Flinku behaviour. Flinku short links are ordinary Universal Links and are subject to the same delivery constraints as any other HTTPS Universal Link. If the OS or `app_links` never delivers the URL to Dart, `Flinku.match()` and your own routing cannot see it. Diagnose by symptom: 1. Symptom: the link opens the app but lands on the wrong screen, or the app opens and nothing routes. Cause: On Flutter 3.38 and later the UIScene lifecycle is mandatory, and `app_links` 6.x only listens on the old app delegate callbacks, so the link never reaches Dart and `getInitialLink()` returns null. Fix: Upgrade to `app_links` 7.0.0 or later, which adds UISceneDelegate support. 2. Symptom: tapping a link opens the app, then a browser flashes, then the app reopens. Cause: `FlutterDeepLinkingEnabled` defaults to true, so when no plugin claims the link the Flutter engine hands it back to iOS, which opens it in the browser. Fix: Set `FlutterDeepLinkingEnabled` to false in `ios/Runner/Info.plist`: ```xml FlutterDeepLinkingEnabled ``` 3. Symptom: links work sometimes and not others, more often on cold start. Cause: `app_links` 7.x emits the launch URL on `uriLinkStream` when the listener attaches. If subscription happens after async startup work the emission is lost, because the stream is not replayed. Fix: Subscribe to `uriLinkStream` synchronously at the start of `initState`, before any `await`. Full write-up: https://docs.flinku.dev/docs/sdk/flutter/#universal-links-not-opening-the-app-on-ios-flutter --- ## Key rules - NEVER put secret API keys (`flk_live_`) in mobile app code. Always proxy secret-key link creation through your backend. - Publishable keys (`flk_pk_`) are safe in the app and required for referral tracking (`setUserId` / `qualifyReferral`). - SDK `baseUrl` must always be your `flku.dev` subdomain even if you have a custom domain - Call `match()` on every cold start, not just first launch - Call `reset()` after handling a deep link to prevent re-firing on next launch. Correct forms: Flutter `await Flinku.reset()`, iOS `Flinku.reset()`, Android `Flinku.reset(context)`, React Native `await flinku.reset()`, Capacitor `await flinku.reset()`, Unity `FlinkuSDK.Instance.Reset()`. Clears match cache only; stored user id and pending referral attribution are preserved (changed in 0.6.0). - For referrals: create links with `referrerId` params, configure with `flk_pk_`, call `setUserId` after signup, call `qualifyReferral` on the rewarded event. Do not DIY with manual track API calls. - React Native / Capacitor / Unity require a `userId` for fingerprint matching. Use any stable unique identifier (Firebase UID, your own user ID, or a device UUID for anonymous users) - Flutter / iOS / Android SDKs do not require a `userId` for match(). Call `setUserId` after auth for referrals. - React Native and Capacitor apps still need native iOS/Android configuration (Associated Domains + intent-filter) --- ## Pricing - Free: $0/mo: 3 projects, 300 links per month, 10k clicks/month - Indie: $12/mo: 10 projects, 5,000 links per month, 300k clicks/month, custom domains, catch-all routing - Studio: $39/mo: Unlimited projects, unlimited links per month, 10M clicks/month, remove "Powered by Flinku" branding No MAU pricing. Flat tiers only. --- ## Docs & links - Full docs: https://docs.flinku.dev - API reference: https://docs.flinku.dev/docs/api/ - Dashboard: https://app.flinku.dev - Landing: https://flinku.dev - Deep Link Tester: https://flinku.dev/tools/deep-link-tester - AASA Validator: https://flinku.dev/tools/aasa-validator