Feature spec
IntermediateCategory
personalization-ux
Build with AI
3–6 hours with FlutterFlow or Lovable
Custom build
1–2 weeks custom dev
Running cost
$0–200/mo depending on third-party API pricing
Works on
Everything it takes to ship API Integration — parts, prompts, and real costs.
Third-party API integration needs an OAuth PKCE flow, a Supabase Edge Function proxy that attaches the user's token to every API call, a user_integrations table with encrypted token storage, and token refresh logic on 401 responses. With FlutterFlow or Lovable you can wire a working integration in 3–6 hours for $0–$200/month depending on API call volume. Never store tokens in client code — always route through an Edge Function.
What API Integration Actually Means in a Mobile App
Connecting a mobile app to a third-party API — Spotify, GitHub, Airtable, Notion, or any OAuth service — means solving four problems in sequence: authenticating the user with the service (OAuth PKCE flow), storing their access and refresh tokens securely, proxying all API calls through a server so tokens never appear in client code, and refreshing tokens automatically before they expire. AI tools build the connection management UI and the Supabase Edge Function proxy pattern well. The failure modes happen at the edges: OAuth callback URLs that point to the wrong domain, tokens stored in plain text, and FlutterFlow's API Connector sending the same hardcoded token to every user rather than fetching the correct one per user_id from the proxy.
What users consider table stakes in 2026
- A Connect button per integration that opens the OAuth flow in a browser sheet without leaving the app
- Connection status card per service showing the connected account email, service logo, and a Disconnect button
- Loading skeleton during API data fetch rather than a blank screen or spinner without context
- Clear, human-readable error messages when the API is down, rate limited, or the token has expired
- Configurable refresh interval for auto-pulling new data from the connected service
- Data preview before the user confirms an action (e.g. 'You are about to sync 42 items from Airtable')
Anatomy of the Feature
Seven components build a secure third-party API integration. The Edge Function proxy and token encryption are the critical security components that AI tools must get right.
OAuth Flow Handler
BackendImplements the Authorization Code Flow with PKCE for the target third-party service. On mobile (Flutter): the flutter_web_auth_2 package opens the provider's authorization URL in an in-app browser and captures the callback URL with the authorization code. A Supabase Edge Function then exchanges the code for access_token and refresh_token via a server-to-server POST — the client secret never appears in the Flutter app. On web (Lovable): the Edge Function handles the full redirect flow and stores tokens server-side.
Note: PKCE (Proof Key for Code Exchange) is required for mobile OAuth — it replaces the client secret with a code_verifier/code_challenge pair generated client-side. flutter_web_auth_2 handles this automatically.
API Request Proxy
BackendA Supabase Edge Function (Deno runtime) that acts as a proxy for all third-party API calls. It reads the calling user's auth.uid() from the JWT, fetches their access_token from the user_integrations table, attaches it to the Authorization header, makes the API request, and returns the JSON response to the Flutter client. The client never sees the raw token.
Note: Use the service_role key for the Edge Function's Supabase client so it can SELECT encrypted tokens regardless of RLS policies on the user_integrations table. Never use the anon key in an Edge Function that reads sensitive rows.
Connection Status Card
UIA card per integration showing the service name, logo, the connected account email (fetched from user_integrations.metadata JSONB), connection date, and a Disconnect button. Status is fetched from user_integrations on load. A 'Connect' CTA appears if no row exists for the service. The card shows a warning badge if the token is expired and the user needs to re-authenticate.
Note: Store the connected account email in the metadata JSONB column at token exchange time — calling the third-party API's 'get current user' endpoint right after OAuth and caching the result avoids a real-time API call just to display the card.
Data Fetcher with Polling
UIA Flutter FutureBuilder or StreamBuilder that calls the Edge Function proxy on mount and at a configurable interval stored in user preferences. Shows a CircularProgressIndicator skeleton during load. The refetch interval defaults to 15 minutes and is adjustable per integration. Uses a polling timer rather than a persistent WebSocket for simplicity.
Note: Cancel the polling timer in the widget's dispose() method to prevent memory leaks when the user navigates away from the integration screen.
Token Refresh Logic
BackendThe Edge Function checks expires_at before every API call. If expires_at < now(), it fires the refresh_token grant to the third-party token endpoint, receives a new access_token and refresh_token, and writes them back to user_integrations. The original API request is then retried with the fresh token. If refresh fails (token revoked or expired), the Edge Function returns a 401 and the client shows a 'Please reconnect' prompt.
Note: Some providers (Google, Spotify) issue new refresh tokens on every refresh. Update both access_token and refresh_token columns on every successful refresh, not just the access_token.
Error State Display
UIA Flutter SnackBar or inline error card that maps HTTP error codes from the proxy Edge Function to human-readable messages: 401 maps to 'Reconnect your account', 429 maps to 'Rate limit reached — try again in N minutes', 503 maps to 'Service temporarily unavailable'. Includes Retry and Reconnect buttons. The error card replaces the loading skeleton in the integration data panel.
Note: Read the retry-after header from the Edge Function response when available and display the exact cooldown time rather than a generic 'try again later' message.
user_integrations Table
DataA Supabase table storing one row per user per connected service. Columns include access_token (encrypted with pgcrypto), refresh_token (encrypted), expires_at timestamp, scopes array, and a metadata JSONB column for service-specific data like connected account email and display name. A UNIQUE constraint on (user_id, service) prevents duplicate connection rows.
Note: Use pgp_sym_encrypt(token, secret_key) in the Edge Function INSERT to encrypt tokens at rest. The secret_key is a long random string stored in Supabase Secrets, never in code. Decrypt with pgp_sym_decrypt in the same Edge Function on SELECT.
The data model
One table stores all third-party API credentials securely. Run this in the Supabase SQL Editor — it is copy-paste ready. The pgcrypto encryption happens in the Edge Function, not in this SQL.
1create extension if not exists pgcrypto;23create table public.user_integrations (4 id uuid primary key default gen_random_uuid(),5 user_id uuid references auth.users(id) on delete cascade not null,6 service text not null,7 access_token text,8 refresh_token text,9 expires_at timestamptz,10 scopes text[],11 metadata jsonb,12 connected_at timestamptz not null default now(),13 constraint user_integrations_user_service_unique unique (user_id, service)14);1516alter table public.user_integrations enable row level security;1718create policy "Users can view own integrations"19 on public.user_integrations for select20 using (auth.uid() = user_id);2122create policy "Users can update own integrations"23 on public.user_integrations for update24 using (auth.uid() = user_id);2526create policy "Users can delete own integrations"27 on public.user_integrations for delete28 using (auth.uid() = user_id);2930create index user_integrations_user_service_idx31 on public.user_integrations (user_id, service);Heads up: The RLS policies allow authenticated users to read and delete their own integration rows — useful for the Connection Status Card and Disconnect button. The service_role key used by Edge Functions bypasses RLS, enabling server-side token INSERT and UPDATE without exposing tokens to the client. Tokens should always be encrypted with pgp_sym_encrypt before INSERT — never stored as plain text.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
Custom development provides full control over token encryption at rest with pgcrypto, webhook registration with third-party services, background token refresh via pg_cron, and multi-account support per service. The only path for enterprise-grade integration requirements.
Step by step
- 1Build the OAuth PKCE flow, Edge Function proxy, and user_integrations table (same as AI path) — this baseline is identical
- 2Add pgcrypto token encryption with a rotation strategy: a Supabase pg_cron job rotates the encryption key annually and re-encrypts all tokens
- 3Register webhooks with each third-party service at connection time — the Edge Function calls the service's webhook registration endpoint and stores the webhook_id in metadata; deregister on Disconnect
- 4Add multi-account support: remove the UNIQUE(user_id, service) constraint, replace with a unique index on (user_id, service, account_id) where account_id is the third-party service's user ID
- 5Implement background token refresh via pg_cron: a job runs every 30 minutes and refreshes all tokens expiring in the next hour, so no API call ever hits an expired token in production
Where this path bites
- Each new third-party API requires individual OAuth app registration, scope analysis, and error handling for that service's unique error codes — budget 0.5–1 day per new service
- Webhook registration and deregistration adds test complexity — each service has different webhook verification signatures and event payload formats
Third-party services you'll need
The integration infrastructure uses Supabase and open-source packages. Third-party API costs depend entirely on which service is integrated and call frequency.
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| Supabase | Edge Functions for OAuth and API proxy (500K invocations/mo free); PostgreSQL for encrypted token storage | Free tier: 500K Edge Function invocations/mo, 500MB DB | Pro $25/mo for 2M invocations and higher storage |
| flutter_web_auth_2 | Flutter OAuth PKCE flow — opens in-app browser and captures callback | Open-source pub.dev package | Free |
| pgcrypto (Postgres extension) | Token encryption at rest using pgp_sym_encrypt/pgp_sym_decrypt | Built into Supabase, zero cost | Free |
| Third-party APIs (Spotify, GitHub, Airtable, etc.) | The actual service being integrated — varies by provider | Most have free tiers for low call volume (approx) | Varies by service; budget $0–100+/mo depending on call frequency |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
Supabase free tier covers Edge Function invocations for 100 users polling at 15-minute intervals. Third-party API free tiers typically sufficient at low user count.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
OAuth callback never returns to the app — user is stuck on the redirect page
Symptom: This is the most common showstopper in AI-generated OAuth integrations. The OAuth provider's redirect URI is configured (in Lovable, in the prompt, or in the third-party developer console) to point to the Lovable preview URL. The preview URL changes every session and is not a stable origin. When the user completes OAuth on the provider's page, the redirect fires to the wrong URL — the app never receives the authorization code.
Fix: Register the OAuth callback URI as the stable published URL (e.g. https://yourapp.lovable.app/auth/callback/spotify) in the provider's developer console before testing the OAuth flow. Update the redirect_uri parameter in the Edge Function oauth-start to use this exact URL. Never use the Lovable preview URL as a redirect URI for any OAuth provider.
API calls return 401 after a few hours even though the token was valid
Symptom: Most OAuth access tokens expire in 1–2 hours (Spotify: 1 hour, GitHub: 8 hours, Google: 1 hour). AI-generated integrations often store the token but skip the refresh logic entirely. When the user returns after a few hours, every API call returns 401 because the token has expired and there is no refresh grant in place.
Fix: In the Edge Function proxy, check expires_at before every API call. If expires_at is within 5 minutes of now() or already past, run the refresh_token grant against the provider's token endpoint, update both access_token and refresh_token in user_integrations, then make the real API request. Return a 401 with 'token_expired_refresh_failed' if the refresh itself fails so the client can show a Reconnect prompt.
Tokens visible in plain text in the Supabase Table Editor
Symptom: AI tools almost always store access_token and refresh_token as plain text in the database. Any team member with Supabase Table Editor access can read every user's third-party API tokens — a serious security exposure, especially for services with billing access (Stripe, Shopify) or personal data (Google, Apple).
Fix: Use pgp_sym_encrypt(token, current_setting('app.encryption_key')) in the Edge Function INSERT. Store the encryption key in Supabase Secrets (not in database settings for production). Decrypt with pgp_sym_decrypt in the same Edge Function on SELECT. After implementing encryption, verify in the Table Editor that the stored value is a binary blob, not a readable string.
FlutterFlow API Connector sends the wrong user's token
Symptom: Developers sometimes configure FlutterFlow's built-in API Connector with a bearer token hardcoded as a header value in the connector settings. This works in testing but sends the developer's own token to every user — all API calls are made on the developer's account regardless of which user is logged in.
Fix: Remove the Authorization header from the FlutterFlow API Connector entirely. Route all calls through the Supabase Edge Function proxy, which reads auth.uid() from the user's Supabase JWT and fetches the correct token for that user from user_integrations. The Edge Function is the only place that knows which token belongs to which user.
Disconnect removes the DB row but the third-party service still has active webhooks
Symptom: Deleting the user_integrations row revokes the token from the app's perspective, but the third-party service still has active webhook subscriptions or event listeners registered against the user's account. The service continues to fire webhook events to the app's endpoint, which now has no matching user_id to process them — creating ghost webhook noise or worse, processing events for a user who has disconnected.
Fix: In the Disconnect Edge Function, before deleting the user_integrations row, call the third-party API's webhook or subscription deletion endpoint using the about-to-be-deleted token. Only delete the DB row after receiving a successful response from the deregistration call. Log the deregistration attempt so you can retry manually if it fails.
Best practices
Route every third-party API call through a Supabase Edge Function proxy — the client (Flutter or browser) must never hold or transmit raw API tokens
Encrypt access_token and refresh_token at rest using pgp_sym_encrypt with a key stored in Supabase Secrets — plain text token storage is a critical security failure
Check token expiry before every API call in the Edge Function and run the refresh grant proactively, not reactively after a 401 — proactive refresh eliminates the latency of a failed request followed by a retry
Register the OAuth callback URI to the stable published URL in the provider's developer console before writing any OAuth code — it is the first thing to configure, not the last
Store the connected account email and display name in the metadata JSONB column at token exchange time so the Connection Status Card renders without an additional API call
Deregister webhooks and revoke tokens at the third-party service before deleting the user_integrations row — ghost webhooks from disconnected users create noise and potential security issues
Add a UNIQUE constraint on (user_id, service) and use INSERT ... ON CONFLICT DO UPDATE for the token storage — re-connecting an already-connected service should refresh the tokens, not create a duplicate row
When You Need Custom Development
AI tools cover single-service integration with OAuth and an Edge Function proxy. These scenarios require custom work:
- The app integrates with 5 or more different third-party services each with unique OAuth flows, token formats, and data schemas requiring individual handling
- Background data sync is required — pulling from APIs every hour even when the user is not in the app, needing pg_cron or an external scheduler rather than client-side polling
- Inbound webhooks from third-party services must be received, verified with HMAC signatures, and processed in real time — requires stable public endpoints and per-service signature verification logic
- Enterprise context where an IT team must approve, audit, and revoke all third-party API connections across the organization with a dedicated admin dashboard
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
How do I keep API keys secure in a mobile app?
Never put API keys or OAuth tokens in your Flutter client code, .env files, or app bundle. Store them in Supabase user_integrations with pgcrypto encryption. All API calls go through a Supabase Edge Function that reads and decrypts the token server-side, makes the API call, and returns only the response data to the client. The client sees the data, never the token.
What is the difference between OAuth and API key integration?
API keys are static credentials that the developer generates once and hardcodes — fine for server-to-server calls but wrong for user-specific integrations. OAuth lets each user individually authorize your app to access their account, giving your app a user-specific access_token with limited scope. OAuth is required for integrations like Spotify (play music for this user), GitHub (access this user's repos), or Google (read this user's calendar).
Can my app connect to multiple accounts of the same service?
Not with the default UNIQUE(user_id, service) constraint — only one connection per user per service. To support multiple accounts (e.g. two Spotify accounts), remove the UNIQUE constraint and add account_id (the third-party service's user ID) to the unique index: UNIQUE(user_id, service, account_id). The Connection Status Card becomes a list of connected accounts for that service. This is a custom development requirement.
How do I handle API rate limits?
Read the retry-after header from the third-party API's 429 response in the Edge Function. Return it to the client as a structured error: { error: 'rate_limited', retry_after_seconds: 60 }. The client displays 'Rate limit reached — try again in 60 seconds' rather than a generic error. For services with low rate limits, add client-side request coalescing so multiple components requesting the same data share one API call.
What happens when a connected service is down?
The Edge Function proxy receives a 5xx response and returns it to the Flutter client as a structured error. The Error State Display component shows 'Service temporarily unavailable' with a Retry button. For non-critical data fetching, implement exponential backoff in the polling timer — back off to 1-hour intervals after 3 consecutive failures rather than hitting a downed service every 15 minutes.
Can I sync data automatically in the background?
Not with client-side polling alone — when the Flutter app is backgrounded or closed, polling stops. True background sync requires either a Supabase pg_cron job that runs server-side on a schedule (available on Supabase Pro) or push notifications that wake the app to trigger a sync. Both are custom development requirements beyond the AI-build scope.
How do I revoke a user's API connection?
The Disconnect flow has two steps: (1) call the third-party service's token revocation endpoint using the stored token, then (2) delete the user_integrations row. Both steps are handled by a 'disconnect-integration' Edge Function. Always deregister webhooks and revoke the token at the provider before deleting the row — if you only delete the row, the service still considers your app authorized and may fire webhooks to your endpoint indefinitely.
Does FlutterFlow support OAuth integrations natively?
FlutterFlow's API Connector supports REST APIs with static bearer tokens but cannot handle per-user OAuth token injection natively. For user-specific OAuth integrations, add flutter_web_auth_2 as a custom package for the in-app browser flow and route all API calls through a Supabase Edge Function proxy that fetches the correct user token. The proxy pattern is non-optional for secure per-user OAuth in FlutterFlow.
Need this feature production-ready?
RapidDev builds api integration into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.