Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

RocketReach

Connect FlutterFlow to RocketReach via the API Calls panel using a FlutterFlow API Group pointed at the RocketReach v2 REST API. Because the Api-Key header is a billable secret that funds per-lookup credits, route all calls through a Firebase Cloud Function or Supabase Edge Function — never paste the key directly into a FlutterFlow API Call. Person-lookup results (emails, phones, confidence grades) bind to widgets via JSON paths.

What you'll learn

  • How RocketReach's Api-Key header and per-lookup credit billing work
  • Why the Api-Key must go in a Cloud Function proxy rather than directly in FlutterFlow
  • How to build a Firebase Cloud Function that calls the RocketReach v2 person-lookup endpoint
  • How to create a FlutterFlow API Group and API Call to call your proxy function
  • How to handle async 'progress' lookup statuses and HTTP 429 Retry-After rate limiting
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate21 min read45 minutesCRM & SalesLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to RocketReach via the API Calls panel using a FlutterFlow API Group pointed at the RocketReach v2 REST API. Because the Api-Key header is a billable secret that funds per-lookup credits, route all calls through a Firebase Cloud Function or Supabase Edge Function — never paste the key directly into a FlutterFlow API Call. Person-lookup results (emails, phones, confidence grades) bind to widgets via JSON paths.

Quick facts about this guide
FactValue
ToolRocketReach
CategoryCRM & Sales
MethodFlutterFlow API Call
DifficultyIntermediate
Time required45 minutes
Last updatedJuly 2026

RocketReach Contact Enrichment in a FlutterFlow App

RocketReach lets your FlutterFlow app look up a person's professional email addresses and phone numbers in real time, given their name and employer. This is powerful for sales apps, recruiting tools, investor-outreach trackers, and any scenario where a mobile user needs verified contact details on demand. The v2 API is straightforward: a GET request to `/api/v2/person/lookup` with query parameters like `name` and `current_employer` returns a JSON payload with an `emails` array and a `phones` array, each item carrying a confidence grade.

The billing model is the detail founders must understand before building. Every successful lookup consumes credits from your RocketReach plan. Plans are paid and metered — check current plan pricing on the RocketReach website, as credit allowances and pricing change. Company lookups via `/api/v2/company/lookup` may be gated behind a separate add-on purchase; confirm your plan includes company data before building that feature, or you will receive an access error that looks like an authentication problem but is actually an entitlement issue.

The critical engineering decision is about the Api-Key header. RocketReach authenticates all API requests with `Api-Key: YOUR_KEY`. In a Dart app compiled by FlutterFlow, any value in an API Call header is embedded in the app binary — decompiling the APK or IPA with freely available tools exposes that key, and anyone who extracts it can burn through your credits. The solution is a Firebase Cloud Function (or Supabase Edge Function) that holds the key in server-side environment variables, performs the lookup, and returns clean JSON to FlutterFlow. Your FlutterFlow API Call then targets the function URL, not RocketReach directly.

Integration method

FlutterFlow API Call

RocketReach exposes a clean v2 REST API at `https://api.rocketreach.co/api/v2` with a simple `Api-Key` header for authentication. FlutterFlow's API Calls panel can reach these endpoints directly, but the Api-Key is a paid, billable credential — a leaked key means strangers consume your lookup credits. For shipped apps, route the API Call through a Firebase Cloud Function or Supabase Edge Function that holds the key server-side, and have FlutterFlow call the function URL. The function performs the RocketReach lookup and returns the results as JSON.

Prerequisites

  • A paid RocketReach account with API access and an Api-Key from rocketreach.co/account (free tier does not include API access)
  • Confirmation that your RocketReach plan includes company lookups if you plan to use /company/lookup (may require a separate purchase)
  • A Firebase project on the Blaze (pay-as-you-go) plan with Cloud Functions enabled, OR a Supabase project with Edge Functions
  • A FlutterFlow project (Free or paid — deployment requires a paid plan)
  • Basic familiarity with JSON responses so you can verify the lookup results during testing

Step-by-step guide

1

Get your RocketReach Api-Key and understand credit billing

Log in to your RocketReach account at rocketreach.co and navigate to your Account Settings. Look for the 'API' or 'Integrations' section — your Api-Key is displayed there as a long alphanumeric string. Copy it and store it securely (a password manager, not a sticky note or a chat message). Before building, spend two minutes understanding the billing model so you don't run out of credits mid-development. Every person lookup that returns contact data consumes one or more credits depending on your plan. Lookups that return no results (person not found) typically do not charge credits, but check your plan's terms. Company lookups (`/company/lookup`) are often a separate entitlement — if your account does not have the company-lookup add-on, calls to that endpoint return an access error. Check your plan at rocketreach.co/pricing or contact RocketReach support to confirm what your subscription includes. Also understand the async status field: when you call `/person/lookup`, RocketReach may return immediately with `status: 'complete'` and a full `emails` array, or it may return `status: 'progress'` with an empty `emails` array while it processes the lookup in the background. Your app needs to handle both cases — polling with a delay when status is 'progress,' or simply informing the user that the result is being prepared. We will handle this in the Cloud Function so FlutterFlow always receives a final result.

Pro tip: During development, run your test lookups against known contacts at your own company to confirm the API is returning data before building the full UI — this avoids wasting credits on test searches for random names.

Expected result: You have your RocketReach Api-Key saved securely and you understand which lookup types (person vs. company) your plan covers. You know credits are charged per successful lookup.

2

Build a Firebase Cloud Function that holds the Api-Key and calls RocketReach

The Cloud Function is the security layer between your FlutterFlow app and RocketReach. It stores your Api-Key in Firebase environment configuration (never in code), receives lookup requests from FlutterFlow, calls RocketReach with the key, handles the async 'progress' status by polling up to a few times before returning, and then sends the final JSON back to FlutterFlow. In the Firebase Console, open your project, go to Functions → Get Started (requires Blaze plan). Create a new HTTPS function called `lookupContact`. The function accepts a POST body with `name` and `currentEmployer` fields, calls the RocketReach v2 person-lookup endpoint, and handles the 'progress' status with a short polling loop (up to 3 retries, 2 seconds apart) before giving up and returning a 'not ready' response. Set your Api-Key as a Firebase Function environment variable via the Firebase console's Functions → Configuration section (key: `rr.api_key`), not hardcoded in the code below. Deploy the function — the deployment takes about 60 seconds. Note the function URL (e.g. `https://us-central1-your-project.cloudfunctions.net/lookupContact`) — you will paste this into FlutterFlow in the next step. For the 429 Retry-After scenario: if RocketReach returns HTTP 429 (too many requests), the response includes a `Retry-After` header indicating how many seconds to wait. Handle this in the Cloud Function by reading the header and returning a 429 with a clear message to FlutterFlow so the app can display a 'Please try again in X seconds' message rather than a generic error.

index.js
1const functions = require('firebase-functions');
2const axios = require('axios');
3
4// Set via Firebase Console → Functions → Configuration:
5// rr.api_key = YOUR_ROCKETREACH_API_KEY
6const RR_API_KEY = functions.config().rr.api_key;
7const RR_BASE_URL = 'https://api.rocketreach.co/api/v2';
8
9async function lookupWithRetry(name, currentEmployer, retries = 3) {
10 for (let i = 0; i < retries; i++) {
11 const res = await axios.get(`${RR_BASE_URL}/person/lookup`, {
12 headers: { 'Api-Key': RR_API_KEY },
13 params: { name, current_employer: currentEmployer }
14 });
15
16 const data = res.data;
17 if (data.status === 'complete' || data.status === 'failed') {
18 return data;
19 }
20 // status === 'progress' — wait and retry
21 if (i < retries - 1) {
22 await new Promise(resolve => setTimeout(resolve, 2000));
23 }
24 }
25 return { status: 'progress', message: 'Lookup is still processing. Try again shortly.' };
26}
27
28exports.lookupContact = functions.https.onRequest(async (req, res) => {
29 res.set('Access-Control-Allow-Origin', '*');
30 if (req.method === 'OPTIONS') {
31 res.set('Access-Control-Allow-Methods', 'POST');
32 res.set('Access-Control-Allow-Headers', 'Content-Type');
33 return res.status(204).send('');
34 }
35
36 const { name, currentEmployer } = req.body;
37 if (!name || !currentEmployer) {
38 return res.status(400).json({ error: 'name and currentEmployer are required' });
39 }
40
41 try {
42 const data = await lookupWithRetry(name, currentEmployer);
43 return res.json(data);
44 } catch (err) {
45 if (err.response?.status === 429) {
46 const retryAfter = err.response.headers['retry-after'] || '60';
47 return res.status(429).json({
48 error: 'Rate limit reached',
49 retry_after_seconds: parseInt(retryAfter, 10)
50 });
51 }
52 if (err.response?.status === 403) {
53 return res.status(403).json({ error: 'Plan does not include this lookup type' });
54 }
55 console.error(err.message);
56 return res.status(500).json({ error: 'RocketReach lookup failed' });
57 }
58});

Pro tip: The polling approach in the function adds up to 6 seconds of wait time on the server side (3 retries × 2 seconds), which keeps well within the 60-second Firebase Cloud Function default timeout. Increase `retries` to 5 if you consistently see 'progress' status on your first calls.

Expected result: The Cloud Function is deployed and returning a URL. Testing it with a POST body like `{"name": "John Smith", "currentEmployer": "Acme Corp"}` via a tool like the Firebase Functions console test panel returns a JSON object with an `emails` array.

3

Create a FlutterFlow API Group and API Call pointing to your Cloud Function

With the Cloud Function deployed and tested, open your FlutterFlow project. Click 'API Calls' in the left navigation panel, then click the '+' (plus) button and choose 'Create API Group.' Name it 'RocketReach.' In the Base URL field, paste the base of your Cloud Function URL — everything up to (but not including) the function name path. For example: `https://us-central1-your-project.cloudfunctions.net`. Leave the Headers section empty — no Api-Key here, since it lives securely in the Cloud Function. Now create an API Call inside this group: click '+' → 'Create API Call' → name it 'LookupPerson.' Set the Method to POST. Set the Path to `/lookupContact`. Click the 'Body' tab, set Content-Type to `application/json`. Click '+ Add Body Field' and add two variables: `name` (type String) and `currentEmployer` (type String). In the body JSON template, both should appear as `{{ name }}` and `{{ currentEmployer }}`. Click the 'Response & Test' tab. Fill in the Variables section: `name` = 'Jane Doe', `currentEmployer` = 'Acme Corporation'. Click 'Test API Call.' If the function is live, you will see a JSON response. For a real person in your network, the response should include an `emails` array with objects containing `email` and `confidence` fields. Click 'Generate JSON Paths' — FlutterFlow will extract paths like `$.emails[0].email`, `$.emails[0].confidence`, `$.status`, and `$.name` that you can bind to UI widgets. If the lookup returns status 'progress' with an empty emails array, it means the Cloud Function's polling retries were exhausted — this is normal for obscure contacts. Your app UI should check `$.status` and display an informational message ('Contact lookup is processing — check back in a few seconds') rather than showing an empty list as though the search failed.

api_call_config.txt
1// API Call reference configuration (match these settings in the FlutterFlow UI)
2// Group Name: RocketReach
3// Base URL: https://us-central1-YOUR_PROJECT.cloudfunctions.net
4//
5// Call Name: LookupPerson
6// Method: POST
7// Path: /lookupContact
8// Content-Type: application/json
9// Body: { "name": "{{ name }}", "currentEmployer": "{{ currentEmployer }}" }
10//
11// Generated JSON Paths:
12// Status: $.status
13// Person name: $.name
14// First email: $.emails[0].email
15// Email confidence: $.emails[0].confidence
16// All emails: $.emails
17// First phone: $.phones[0].number
18// LinkedIn URL: $.linkedin_url

Pro tip: If the test returns a 403 error with 'Plan does not include this lookup type,' your RocketReach plan covers person lookups but not company lookups. For company lookup, you need a separate entitlement — contact RocketReach sales to add it to your account.

Expected result: The 'LookupPerson' API Call test succeeds and FlutterFlow shows a response with an emails array. JSON Path variables for email, confidence, status, and phone are generated and visible in the Response & Test tab.

4

Bind lookup results to your app screen and handle the async 'progress' status

On your contact-lookup screen in FlutterFlow, you need an input field for the person's name, an input field for their employer, a 'Find Contact' button, and a results area that shows the emails and confidence grades. Create two Page State variables: `lookupStatus` (String, default: 'idle') and `lookupEmails` (List of JSON, default: empty list). Add a TextField widget bound to a local variable `nameInput` and another for `employerInput`. Add a Button widget with the text 'Find Contact.' For the Button's action, open the Action Flow Editor (select the button → Actions panel → '+' to add action). Add a 'Set Page State' action that sets `lookupStatus` to 'loading.' Then add a 'Backend/API Call' action → select RocketReach → LookupPerson. Set `name` to the `nameInput` variable and `currentEmployer` to the `employerInput` variable. After the call, add a 'Set Page State' action that sets `lookupStatus` to the JSON path value `$.status` from the response, and sets `lookupEmails` to the JSON path `$.emails` from the response. In the UI results area, add a Conditional Widget that checks `lookupStatus`: if 'loading,' show a CircularProgressIndicator; if 'progress,' show a Text widget saying 'Lookup is processing — results may take a moment'; if 'failed' or 'not_found,' show 'No contact found'; otherwise show the emails ListView. Inside the emails ListView, bind each item to a Row with two Text widgets: one for `email` and one for `confidence` (styled in a lighter color). Add a copy-to-clipboard IconButton using FlutterFlow's built-in clipboard action bound to each email string. This flow gives users clear feedback at every stage — they are never staring at a blank screen wondering if the lookup worked. Because each tap of 'Find Contact' consumes a credit, add a client-side guard: disable the button and show a spinner during the `loading` state so users cannot accidentally double-tap and trigger two lookups.

page_state_design.txt
1// Page State variables to create in FlutterFlow:
2// - lookupStatus: String, initial value = 'idle'
3// - lookupEmails: List<dynamic>, initial value = []
4//
5// Button action sequence in Action Flow Editor:
6// 1. Set State: lookupStatus = 'loading'
7// 2. API Call: RocketReach → LookupPerson
8// Variables: name = nameInput, currentEmployer = employerInput
9// 3. Set State: lookupStatus = API Response → $.status
10// 4. Set State: lookupEmails = API Response → $.emails
11//
12// Conditional widget logic:
13// lookupStatus == 'loading' → CircularProgressIndicator
14// lookupStatus == 'progress' → Text('Lookup processing, check back shortly')
15// lookupStatus == 'failed' → Text('No contact found')
16// lookupStatus == 'complete' → ListView of email cards
17// else (idle) → empty / placeholder text

Pro tip: Wrap the 'Find Contact' button in a Conditional visibility or disable it when `lookupStatus == 'loading'` — this prevents accidental double-taps that would fire two paid lookups for the same person.

Expected result: The lookup screen shows a spinner during the API call, then displays a list of verified email addresses with confidence grades when the status is 'complete.' A copy-to-clipboard button works on each email. If the lookup is still processing, the user sees an informational message rather than an empty screen.

5

Cache results in Firestore to avoid re-billing for the same contact

Because every lookup costs credits, you should cache results in Firestore (or Supabase) so the same person is only looked up once. Without caching, every time a user opens a contact's detail screen, your app would trigger a new paid API call — multiplying costs rapidly in a team environment. The caching pattern works like this: before calling the Cloud Function, check Firestore for an existing enrichment record keyed by a normalized version of the person's name + employer (e.g. `jane_doe_acme_corp`). If a record exists and is less than 30 days old, skip the API call and display the cached emails. If no record exists, call the Cloud Function, and on success write the result to Firestore with a timestamp. In FlutterFlow, implement this with a Backend Query on the contact detail page that reads from a Firestore collection called `enriched_contacts` with a document ID equal to the normalized key. In the 'On Page Load' action, check if the query returned data (use a Conditional action based on whether the Backend Query has results). If yes, skip straight to displaying the cached emails. If no, fire the LookupPerson API Call, write the result back to Firestore using an 'Update Firestore Record' action, and then display the returned emails. Also add a 'Refresh' button that bypasses the cache — some users legitimately need an up-to-date lookup after a contact changes jobs. The Refresh button can set a flag in Page State that tells the action flow to skip the cache check and write a fresh result back to Firestore after calling RocketReach.

caching_design.txt
1// Firestore collection: enriched_contacts
2// Document ID: normalized key, e.g. 'jane_doe_acme_corp'
3// Fields:
4// emails: List<Map> (from $.emails)
5// phones: List<Map> (from $.phones)
6// linkedin_url: String
7// cached_at: Timestamp
8// lookup_name: String
9// lookup_employer: String
10//
11// On page load action flow (pseudocode):
12// 1. Query Firestore: enriched_contacts/{normalizedKey}
13// 2. If document exists AND cached_at > NOW - 30 days:
14// Set Page State: lookupEmails = document.emails
15// Set lookupStatus = 'complete'
16// 3. Else:
17// Call Cloud Function → LookupPerson
18// Write result to Firestore enriched_contacts/{normalizedKey}
19// Set Page State from response

Pro tip: Normalize the cache key consistently — lowercase and replace spaces with underscores: `john_smith_acme_corp`. This prevents the same contact from being cached under slightly different key variations (e.g. 'John Smith' vs 'john smith') and accidentally triggering duplicate paid lookups.

Expected result: Opening a previously looked-up contact's detail screen shows their emails instantly from the Firestore cache with no API call fired. The Firestore console shows the enriched_contacts collection populating with cached records. New contacts trigger one API call each and write correctly to the cache.

6

Handle 429 rate limiting and add company lookup (if your plan includes it)

RocketReach returns HTTP 429 with a `Retry-After` header when you hit the rate limit for your plan. The Cloud Function we built earlier passes this through to FlutterFlow as a 429 response with a `retry_after_seconds` field in the JSON body. You need to surface this gracefully in the app rather than showing a generic error. In your FlutterFlow action flow, after the LookupPerson API Call, add a Conditional action that checks the HTTP status code of the response. FlutterFlow's API Call result includes a `statusCode` field — if it equals 429, set a Page State variable `rateLimitMessage` to a formatted string like 'Too many lookups. Please wait 60 seconds and try again.' Display this in a SnackBar or an inline error Text widget bound to `rateLimitMessage`. For company lookup (if your RocketReach plan includes it), add a second API Call in the RocketReach group: name it 'LookupCompany,' method GET, path `/company/lookup`, with a query variable `name` (the company name). In the Cloud Function, add a separate handler for company lookups: receive a `companyName` field in the POST body, call `/api/v2/company/lookup?name=...`, and return the result. Company lookup returns fields like `name`, `domain`, `linkedin_url`, `size`, and `revenue` — map these with JSON Paths in FlutterFlow and bind them to a company-detail screen. Before spending time building the company-lookup screen, confirm with RocketReach support that your plan includes it — a 403 error from the company endpoint means you need to add the company-lookup add-on to your subscription. Keep this feature gated behind a check in the UI so that if the Cloud Function returns a 403, users see 'Company lookup requires a plan upgrade' rather than a confusing API error.

index.js
1// Add to the Cloud Function to handle company lookup:
2exports.lookupCompany = functions.https.onRequest(async (req, res) => {
3 res.set('Access-Control-Allow-Origin', '*');
4 if (req.method === 'OPTIONS') {
5 res.set('Access-Control-Allow-Methods', 'POST');
6 res.set('Access-Control-Allow-Headers', 'Content-Type');
7 return res.status(204).send('');
8 }
9
10 const { companyName } = req.body;
11 if (!companyName) {
12 return res.status(400).json({ error: 'companyName is required' });
13 }
14
15 try {
16 const rr = await axios.get(`${RR_BASE_URL}/company/lookup`, {
17 headers: { 'Api-Key': RR_API_KEY },
18 params: { name: companyName }
19 });
20 return res.json(rr.data);
21 } catch (err) {
22 if (err.response?.status === 403) {
23 return res.status(403).json({ error: 'Company lookup requires a plan upgrade' });
24 }
25 if (err.response?.status === 429) {
26 const retryAfter = err.response.headers['retry-after'] || '60';
27 return res.status(429).json({ error: 'Rate limited', retry_after_seconds: parseInt(retryAfter, 10) });
28 }
29 return res.status(500).json({ error: 'Company lookup failed' });
30 }
31});

Pro tip: Company lookups can reveal a company's size, domain, and revenue — very useful for a sales qualification screen in your FlutterFlow app. If you're building a B2B sales tool, pair the company lookup with the person lookup on the same detail screen for maximum context.

Expected result: When the RocketReach rate limit is hit, the app shows a human-readable 'Please wait X seconds' message rather than a generic error. If company lookup is enabled on the plan, a company search screen returns domain, size, and revenue fields and binds them to Text widgets. If not enabled, a 403 returns a clear 'plan upgrade needed' message.

Common use cases

Sales prospecting app with on-demand contact lookup

A sales rep opens the FlutterFlow app, types a prospect's name and company, and taps 'Find Contact.' The app calls the Cloud Function, which queries RocketReach and returns verified email addresses with confidence grades. The rep can copy the best email to the clipboard or tap to compose a message — all without leaving the mobile app.

FlutterFlow Prompt

Build a contact-lookup screen in FlutterFlow where a sales rep enters a person's name and their employer, taps a search button, and sees a list of verified email addresses with confidence scores returned from RocketReach, each with a copy-to-clipboard button.

Copy this prompt to try it in FlutterFlow

Investor-outreach tracker with enriched contact profiles

A startup founder maintains a list of target investors in the app. Tapping an investor card triggers a RocketReach lookup that fetches their work email and phone number and saves the result to Firestore so the same lookup is not re-billed on future views. The detail screen shows the enriched profile alongside notes the founder has added.

FlutterFlow Prompt

Create an investor-outreach CRM in FlutterFlow where each investor card can trigger a RocketReach contact lookup to fill in email and phone, with results cached in Firestore so the same contact is only billed once, and the user can add personal notes to each record.

Copy this prompt to try it in FlutterFlow

Recruiting app that enriches candidate profiles

A recruiter uses a FlutterFlow app to manage open roles. When reviewing a candidate whose profile is missing contact details, they tap 'Enrich from RocketReach.' The app fires a lookup using the candidate's name and their listed employer, adds the returned email to the candidate record in Supabase, and marks the profile as enriched to prevent future re-charges.

FlutterFlow Prompt

Build a recruitment-app screen in FlutterFlow that shows candidate profiles, and includes an 'Enrich Contact' button that calls RocketReach with the candidate's name and employer, then saves the returned verified email and phone to the candidate's record in Supabase.

Copy this prompt to try it in FlutterFlow

Troubleshooting

API Call returns 403 Forbidden for /company/lookup but person lookups work fine

Cause: Company lookups in RocketReach are often gated behind a separate paid add-on that is not included in the base person-lookup plan. A 403 on the company endpoint does not mean your Api-Key is wrong — it means your account subscription does not include company-level data.

Solution: Log in to your RocketReach account and check your subscription tier under Account Settings → Subscription. If company data is not listed, contact RocketReach sales to add the company-lookup entitlement. In the meantime, gate the company-lookup feature in your app UI so it only shows for users whose account has the entitlement, and display 'Company lookup requires a plan upgrade' when the Cloud Function returns a 403.

Lookup returns status 'progress' with an empty emails array every time, even for well-known contacts

Cause: RocketReach processes some lookups asynchronously, especially for contacts it has not previously indexed. The 'progress' status means the lookup has been queued but not yet completed. The Cloud Function's polling retries (3 attempts × 2 seconds) may be exhausting before the result is ready.

Solution: Increase the polling retries in the Cloud Function from 3 to 5 or 6, and increase the delay between retries from 2 to 3 seconds. If 'progress' responses persist for a specific contact, the person may genuinely not be in RocketReach's database. In the FlutterFlow UI, show a 'Lookup processing' state with a 'Check Again' button that re-fires the API Call without consuming an additional credit (status: 'progress' usually does not charge).

typescript
1// Update the polling loop in lookupWithRetry:
2async function lookupWithRetry(name, currentEmployer, retries = 5) {
3 for (let i = 0; i < retries; i++) {
4 const res = await axios.get(`${RR_BASE_URL}/person/lookup`, {
5 headers: { 'Api-Key': RR_API_KEY },
6 params: { name, current_employer: currentEmployer }
7 });
8 if (res.data.status === 'complete' || res.data.status === 'failed') return res.data;
9 if (i < retries - 1) await new Promise(r => setTimeout(r, 3000));
10 }
11 return { status: 'progress', message: 'Still processing. Try the Check Again button.' };
12}

HTTP 429 error with no Retry-After information reaching FlutterFlow

Cause: The Cloud Function is not correctly reading or forwarding the Retry-After header from the RocketReach response before returning the 429 to FlutterFlow.

Solution: Confirm the Cloud Function code reads `err.response.headers['retry-after']` (lowercase — Node.js lowercases all HTTP headers). If the header is missing, default to 60 seconds as a safe retry interval. Update the Cloud Function to always include `retry_after_seconds` in the 429 JSON response body so FlutterFlow's conditional action can read it.

XMLHttpRequest error when testing the API Call in FlutterFlow's Run mode (web preview)

Cause: CORS policy blocks the browser from calling the Cloud Function in FlutterFlow's web-based Run mode. Native iOS and Android builds do not enforce CORS, but the browser does.

Solution: Confirm the Cloud Function includes CORS headers in every response: `res.set('Access-Control-Allow-Origin', '*')` and a handler for OPTIONS preflight requests. The complete CORS handler is already included in the Cloud Function code in Step 2. If you already added it, redeploy the function — sometimes a redeployment is needed for CORS headers to take effect on all routes.

Best practices

  • Never put the RocketReach Api-Key in a FlutterFlow API Call header or variable — it must live in a Cloud Function environment variable to prevent credit theft via app decompilation.
  • Cache every successful lookup result in Firestore or Supabase with a timestamp so the same contact is only billed once; re-lookup only on explicit user refresh or after a cache expiry period.
  • Always check the lookup status field in the API response before displaying emails — handle 'progress', 'failed', and 'complete' states with distinct UI feedback so users are never confused by an empty screen.
  • Disable the 'Find Contact' button and show a loading indicator during an in-flight lookup to prevent accidental double-taps that would trigger two paid credit charges.
  • Add a 429 rate-limit handler that reads the retry_after_seconds field from your Cloud Function's response and displays a human-readable countdown message to the user.
  • Confirm your RocketReach plan includes company lookups before building that feature — a 403 on /company/lookup is an entitlement issue, not an auth failure, and requires a plan upgrade.
  • Normalize contact cache keys (lowercase, underscores instead of spaces) to avoid duplicate cache entries for the same person under slightly different name spellings.
  • Log Cloud Function invocations in Firebase to monitor lookup volume — if credits are depleting faster than expected, check the logs for accidental uncached repeat lookups before investigating other causes.

Alternatives

Frequently asked questions

Can I put the RocketReach Api-Key directly in a FlutterFlow API Call header?

Only for quick local testing — never for a shipped app. FlutterFlow compiles to Dart code that runs on users' devices, and any value in an API Call header is embedded in the compiled binary. Anyone who decompiles your app (a one-command process with freely available tools) can extract the key and spend your lookup credits. Always route the key through a Firebase Cloud Function or Supabase Edge Function for production apps.

What does 'status: progress' in the RocketReach response mean?

RocketReach sometimes processes lookups asynchronously, especially for contacts it hasn't recently indexed. A 'progress' status means the lookup has been queued but the result is not yet ready — no emails or phones are returned yet. The Cloud Function we built polls the endpoint up to five times with short delays. If it still returns 'progress,' show the user a 'processing' message and a 'Check Again' button. This status typically does not charge a credit.

Why is /company/lookup returning a 403 when my person lookups work fine?

Company lookups are often a separate entitlement on RocketReach's plans, not included in the base person-lookup subscription. A 403 on the company endpoint is an account-level access issue, not an authentication failure. Log in to RocketReach Account Settings and check your subscription, or contact RocketReach sales to add company-lookup access to your plan.

How do I avoid being charged twice for the same contact?

Cache the lookup result in Firestore or Supabase after the first successful call, keyed by a normalized version of the person's name and employer. On subsequent views of the same contact, check the cache first and skip the RocketReach API call entirely. Set a cache expiry (e.g., 30 days) and offer a manual 'Refresh' button for users who need up-to-date contact details after a job change.

Does FlutterFlow's web Run mode work for testing RocketReach calls?

Run mode uses a browser environment, which enforces CORS. If your Cloud Function includes proper CORS headers (Access-Control-Allow-Origin: * and OPTIONS preflight handling), web Run mode works fine. Native iOS and Android builds do not enforce CORS. If you see XMLHttpRequest errors only in Run mode, check that the CORS headers are being set in every Cloud Function response.

Can I use RocketReach to look up contacts in bulk for my app?

RocketReach offers a bulk-lookup endpoint on higher-tier plans, but it is a background operation, not a real-time API call — it accepts a CSV and returns results asynchronously. For a FlutterFlow app with on-demand user lookups, the /person/lookup endpoint (one contact at a time) is the right approach. Bulk enrichment is better handled in an offline pipeline that populates your Firestore or Supabase database, which FlutterFlow then reads without any per-view API calls.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.