Skip to main content
RapidDev - Software Development Agency
App Featurespersonalization-ux20 min read

How to Add API Integration to Your App — Copy-Paste Prompts Included

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.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Intermediate

Category

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

Mobile

Everything it takes to ship API Integration — parts, prompts, and real costs.

TL;DR

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.

Layers:UIDataBackend

OAuth Flow Handler

Backend

Implements 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

Backend

A 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

UI

A 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

UI

A 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

Backend

The 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

UI

A 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

Data

A 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.

schema.sql
1create extension if not exists pgcrypto;
2
3create 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);
15
16alter table public.user_integrations enable row level security;
17
18create policy "Users can view own integrations"
19 on public.user_integrations for select
20 using (auth.uid() = user_id);
21
22create policy "Users can update own integrations"
23 on public.user_integrations for update
24 using (auth.uid() = user_id);
25
26create policy "Users can delete own integrations"
27 on public.user_integrations for delete
28 using (auth.uid() = user_id);
29
30create index user_integrations_user_service_idx
31 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.

Hand-built by developersFit for this feature:

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

  1. 1Build the OAuth PKCE flow, Edge Function proxy, and user_integrations table (same as AI path) — this baseline is identical
  2. 2Add pgcrypto token encryption with a rotation strategy: a Supabase pg_cron job rotates the encryption key annually and re-encrypts all tokens
  3. 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
  4. 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
  5. 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.

ServiceWhat it doesFree tierPaid from
SupabaseEdge Functions for OAuth and API proxy (500K invocations/mo free); PostgreSQL for encrypted token storageFree tier: 500K Edge Function invocations/mo, 500MB DBPro $25/mo for 2M invocations and higher storage
flutter_web_auth_2Flutter OAuth PKCE flow — opens in-app browser and captures callbackOpen-source pub.dev packageFree
pgcrypto (Postgres extension)Token encryption at rest using pgp_sym_encrypt/pgp_sym_decryptBuilt into Supabase, zero costFree
Third-party APIs (Spotify, GitHub, Airtable, etc.)The actual service being integrated — varies by providerMost 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

$0/mo

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

1

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

2

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

3

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

4

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

5

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

6

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

7

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.

Get a fixed-price quote

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.

RapidDev

Need this feature production-ready?

RapidDev builds api integration into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.