Back to Articles
    Integrations
    May 20, 2026
    8 min read

    OutFlo - Apollo Integration: Sync leads from Apollo to OutFlo

    Build a scheduled n8n workflow that fetches leads from Apollo and syncs them automatically.

    Tushar Singla
    Tushar SinglaFounder & CEO, OutFlo

    This guide walks you through setting up an automated n8n workflow that pulls contacts from Apollo and syncs them into an Outflo lead list. The workflow runs on a schedule — the first run creates a new lead list in Outflo, and every subsequent run adds new leads to that same list automatically.


    What You'll Need

    Before starting, make sure you have the following ready:

    Read the steps below to configure.


    Step 1: Get Your Apollo API Key

    1. 1.Log into Apollo and navigate to Settings → Integrations.
    2. 2.Click Connect next to "Apollo API."
    3. 3.Click API Keys → Create new key.
    4. 4.Enter a name (e.g., "Outflo Sync") and a description.
    5. 5.When prompted to select API endpoint access, choose Search API — specifically the People Search / Contacts Search permission. This is the only permission the workflow needs.
    6. 6.Click Create API key.
    7. 7.Copy the key and store it somewhere safe. Anyone with this key can take actions in your Apollo account.
    Note
    API Key Header Requirement: As of September 2024, Apollo requires API keys to be passed in the request header (not query parameters). The workflow is already configured to do this.

    Step 2: Get Your Outflo API Key

    1. 1.Log into Outflo and go to https://reach.outflo.io/integration/api.
    2. 2.Generate or copy your API key from the API settings page.
    3. 3.Store the key safely alongside your Apollo key.

    Step 3: Import the Workflow into n8n

    1. 1.Open your n8n instance.
    2. 2.Go to Workflows → Import from File (or use the `…` menu on the workflows page).
    3. 3.Select the `apollo_to_outflo_n8n_workflow.json` file and import it.
    4. 4.The workflow will appear with the name "Apollo → Outflo Lead Sync."

    Step 4: Create API Credentials in n8n

    You need two HTTP Header Auth credentials — one for Apollo, one for Outflo.

    Apollo API Key credential:

    1. 1.In n8n, go to Settings → Credentials → Add Credential.
    2. 2.Search for "Header Auth" and select it.
    3. 3.Set Name to `x-api-key`.
    4. 4.Set Value to your Apollo API key (from Step 1).
    5. 5.Save the credential with a label like "Apollo API Key."

    Outflo API Key credential:

    1. 1.Create another Header Auth credential.
    2. 2.Set Name to `x-api-key`.
    3. 3.Set Value to your Outflo API key (from Step 2).
    4. 4.Save with a label like "Outflo API Key."

    Step 5: Connect Credentials to Workflow Nodes

    Open the imported workflow and connect each credential to the correct node:

    1. 1.Click the "Fetch Apollo Contacts" node → under Credential, select your "Apollo API Key" credential.
    2. 2.Click the "Create Lead List" node → under Credential, select your "Outflo API Key" credential.
    3. 3.Click the "Add Leads to List" node → under Credential, select your "Outflo API Key" credential (same one).

    Step 6: Customize Apollo Search Filters (Optional)

    By default, the workflow fetches the most recent 100 contacts from Apollo. To narrow this down to a specific segment, open the "Fetch Apollo Contacts" node and edit the JSON body.

    Some useful filter examples:

    Apollo Search Filters
    {
      "sort_ascending": false,
      "page": 1,
      "per_page": 100,
      "person_titles": ["CEO", "CTO", "VP Engineering"],
      "q_organization_domains": "example.com\ntargetcompany.com",
      "contact_label_ids": ["YOUR_LABEL_ID_HERE"]
    }

    Refer to Apollo's API documentation for the full list of available search filters.


    Step 7: Test the Workflow

    1. 1.Click "Test Workflow" (or "Execute Workflow") to trigger a manual run.
    2. 2.Watch the execution flow through each node. On this first run, the path should be:
    • Fetch Apollo Contacts → pulls contacts from Apollo
    • Build CSV → converts contacts to CSV, detects this is the first run (no saved list ID)
    • Has Leads? → confirms contacts were found
    • Is First Run? → routes to the "true" branch
    • Create Lead List → creates a new lead list in Outflo with the CSV
    • Save List ID → extracts the `lead_list_id` from Outflo's response and stores it in n8n's workflow static data
    1. 3.Verify the lead list appeared in your Outflo dashboard with the correct contacts.

    Step 8: Verify Subsequent Runs

    Run the workflow a second time (manually or wait for the schedule). This time:

    • Build CSV reads the saved list ID from static data → `isFirstRun` is now `false`.
    • The flow routes to the "Read List ID""Add Leads to List" path.
    • New contacts are appended to the same list created in Step 7.

    If this works, you're all set.


    Step 9: Activate the Workflow

    Once testing confirms both paths work:

    1. 1.Toggle the workflow to Active using the switch in the top-right corner of the workflow editor.
    2. 2.The workflow will now run automatically every hour (or whatever interval you configured).

    How the Workflow Operates

    n8n Apollo to Outflo lead sync workflow canvas
    n8n Apollo to Outflo lead sync workflow canvas

    Here's a summary of what happens each time the workflow runs:

    apollo_to_outflo_n8n_workflow.json
    Poll Every Hour
      → Fetch Apollo Contacts (POST /api/v1/contacts/search)
        → Build CSV (extracts contacts, builds CSV, checks static data for lead list ID)
          → Has Leads? (totalContacts > 0?)
            → Yes → Is First Run? (no saved lead list ID?)
              → Yes → Create Lead List (POST /lead-lists)
                        → Save List ID (persists ID in n8n static data)
              → No  → Read List ID (reads ID from static data)
                        → Add Leads to List (POST /lead-lists/leads)
            → No  → No New Leads (skip)

    The workflow uses n8n's workflow static data (`$getWorkflowStaticData('global')`) to persist the Outflo lead list ID between runs. This means no manual configuration is needed after the first run — the list ID is saved automatically and reused for every subsequent execution.


    Adjusting the Polling Interval

    To change how often the workflow runs:

    1. 1.Open the "Poll Every Hour" node.
    2. 2.Change the interval — for example, set it to run every 30 minutes, every 6 hours, or once a day.
    3. 3.Save and reactivate the workflow.

    Troubleshooting

    "No contacts found in Apollo response" Your Apollo search filters may be too restrictive, or the API key might not have the correct permissions. Test the same request in a tool like Postman to verify.

    "Could not extract lead_list_id from Create Lead List response" The Save List ID node couldn't find the list ID in Outflo's response. Check the execution log in n8n to see the raw response from Outflo — the ID field might be nested differently. Update the extraction logic in the Save List ID code node if needed.

    "No leadListId found in workflow static data" This means the first run didn't save the list ID. Common causes: the Create Lead List request failed silently, or the workflow was re-imported (static data is tied to the workflow ID — re-importing creates a new ID with empty static data). Run the workflow once manually to recreate the list.

    Leads are being added to a new list every time Static data may have been cleared, or the workflow was duplicated. Check if the Save List ID node is executing successfully on the first run by inspecting its output in the execution log.


    Resetting the Workflow

    If you want to start fresh with a new lead list:

    1. 1.Open the Save List ID node.
    2. 2.Temporarily change the code to: `$getWorkflowStaticData('global').leadListId = '';`
    3. 3.Run the workflow manually once — this clears the stored ID.
    4. 4.Revert the code change.
    5. 5.The next run will create a brand new lead list in Outflo.

    Alternatively, delete the workflow and re-import the JSON file — a fresh import starts with empty static data.


    API Endpoints Used

    ServiceEndpointPurpose
    Apollo`POST /api/v1/contacts/search`Fetch contacts matching your filters
    Outflo`POST /api/public/lead-lists`Create a new lead list (first run)
    Outflo`POST /api/public/lead-lists/leads`Add leads to an existing list (subsequent runs)

    CSV Fields Synced

    The following contact fields are extracted from Apollo and sent to Outflo:

    FieldApollo Source
    First Name`first_name`
    Last Name`last_name`
    Email`email`
    Title`title`
    Company`organization.name`
    LinkedIn URL`linkedin_url`
    Phone`phone_numbers[0].sanitized_number`
    City`city`
    State`state`
    Country`country`

    Outflo uses the LinkedIn URL column to deduplicate leads within a list.


    Download Workflow JSON

    You can copy or download the full workflow JSON file below, then import it directly into your n8n workspace:

    apollo_to_outflo_n8n_workflow.json
    {
      "name": "Apollo → Outflo Lead Sync",
      "nodes": [
        {
          "parameters": {
            "rule": {
              "interval": [
                {
                  "field": "hours",
                  "hoursInterval": 1
                }
              ]
            }
          },
          "id": "schedule-trigger",
          "name": "Poll Every Hour",
          "type": "n8n-nodes-base.scheduleTrigger",
          "typeVersion": 1.2,
          "position": [0, 0]
        },
        {
          "parameters": {
            "method": "POST",
            "url": "https://api.apollo.io/api/v1/contacts/search",
            "authentication": "genericCredentialType",
            "genericAuthType": "httpHeaderAuth",
            "sendBody": true,
            "specifyBody": "json",
            "jsonBody": "={{ JSON.stringify({ sort_ascending: false, page: 1, per_page: 100 }) }}",
            "options": {
              "response": {
                "response": {
                  "fullResponse": true
                }
              }
            }
          },
          "id": "apollo-fetch-contacts",
          "name": "Fetch Apollo Contacts",
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [250, 0],
          "credentials": {
            "httpHeaderAuth": {
              "id": "APOLLO_HEADER_AUTH_CREDENTIAL_ID",
              "name": "Apollo API Key"
            }
          },
          "notes": "SETUP: Create HTTP Header Auth credential → Name: x-api-key, Value: your Apollo API key (Search type)."
        },
        {
          "parameters": {
            "mode": "runOnceForAllItems",
            "jsCode": "// ─── Check workflow static data for existing lead list ID ───\nconst staticData = $getWorkflowStaticData('global');\nconst leadListId = staticData.leadListId || '';\nconst isFirstRun = !leadListId;\n\n// ─── Extract contacts from Apollo response ───\nconst response = $input.all();\nconst body = response[0].json.body || response[0].json;\nconst contacts = body.contacts || body.people || [];\n\nif (!contacts.length) {\n  return [{ json: { totalContacts: 0, isFirstRun } }];\n}\n\n// ─── Build CSV ───\nconst headers = [\n  'First Name','Last Name','Email','Title','Company',\n  'LinkedIn URL','Phone','City','State','Country'\n];\n\nconst escapeCSV = (val) => {\n  if (val == null) return '';\n  const s = String(val);\n  if (s.includes(',') || s.includes('"') || s.includes('\\n')) {\n    return '"' + s.replace(/"/g, '""') + '"';\n  }\n  return s;\n};\n\nconst rows = contacts.map(p => [\n  escapeCSV(p.first_name),\n  escapeCSV(p.last_name),\n  escapeCSV(p.email),\n  escapeCSV(p.title),\n  escapeCSV(p.organization?.name || p.organization_name || ''),\n  escapeCSV(p.linkedin_url || ''),\n  escapeCSV(p.phone_numbers?.[0]?.sanitized_number || p.phone || ''),\n  escapeCSV(p.city),\n  escapeCSV(p.state),\n  escapeCSV(p.country)\n]);\n\nconst csvContent = [headers.join(','), ...rows.map(r => r.join(','))].join('\\n');\nconst csvBuffer = Buffer.from(csvContent, 'utf-8');\n\nconst now = new Date();\nconst listName = \`Apollo Sync - \${now.toISOString().slice(0, 16).replace('T', ' ')}\`;\n\nreturn [{\n  json: {\n    totalContacts: contacts.length,\n    isFirstRun,\n    listName\n  },\n  binary: {\n    csv: {\n      data: csvBuffer.toString('base64'),\n      mimeType: 'text/csv',\n      fileName: 'apollo_leads.csv'\n    }\n  }\n}];\n"
          },
          "id": "build-csv",
          "name": "Build CSV",
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [500, 0]
        },
        {
          "parameters": {
            "conditions": {
              "options": {
                "caseSensitive": true,
                "leftValue": "",
                "typeValidation": "loose"
              },
              "conditions": [
                {
                  "id": "has-leads-check",
                  "leftValue": "={{ $json.totalContacts }}",
                  "rightValue": 0,
                  "operator": {
                    "type": "number",
                    "operation": "gt"
                  }
                }
              ],
              "combinator": "and"
            },
            "options": {}
          },
          "id": "check-has-leads",
          "name": "Has Leads?",
          "type": "n8n-nodes-base.if",
          "typeVersion": 2.2,
          "position": [750, 0]
        },
        {
          "parameters": {
            "conditions": {
              "options": {
                "caseSensitive": true,
                "leftValue": "",
                "typeValidation": "loose"
              },
              "conditions": [
                {
                  "id": "first-run-check",
                  "leftValue": "={{ $json.isFirstRun }}",
                  "rightValue": true,
                  "operator": {
                    "type": "boolean",
                    "operation": "true"
                  }
                }
              ],
              "combinator": "and"
            },
            "options": {}
          },
          "id": "check-first-run",
          "name": "Is First Run?",
          "type": "n8n-nodes-base.if",
          "typeVersion": 2.2,
          "position": [1000, -50]
        },
        {
          "parameters": {
            "method": "POST",
            "url": "https://live.outflo.in/api/public/lead-lists",
            "authentication": "genericCredentialType",
            "genericAuthType": "httpHeaderAuth",
            "sendBody": true,
            "contentType": "multipart-form-data",
            "bodyParameters": {
              "parameters": [
                {
                  "parameterType": "formData",
                  "name": "name",
                  "value": "={{ $json.listName }}"
                },
                {
                  "parameterType": "formBinaryData",
                  "name": "csv",
                  "inputDataFieldName": "csv"
                }
              ]
            },
            "options": {}
          },
          "id": "outflo-create-list",
          "name": "Create Lead List",
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [1300, -150],
          "credentials": {
            "httpHeaderAuth": {
              "id": "OUTFLO_HEADER_AUTH_CREDENTIAL_ID",
              "name": "Outflo API Key"
            }
          },
          "notes": "First run only. Creates a new lead list with CSV."
        },
        {
          "parameters": {
            "mode": "runOnceForAllItems",
            "jsCode": "// ─── Save the lead list ID to workflow static data ───\nconst staticData = $getWorkflowStaticData('global');\nconst response = $input.all()[0].json;\n\n// Try common response shapes from Outflo\nconst listId = response.data?.lead_list_id\n  || response.data?.leadListId\n  || response.data?.id\n  || response.lead_list_id\n  || response.leadListId\n  || response.id\n  || '';\n\nif (!listId) {\n  throw new Error(\n    'Could not extract lead_list_id from Create Lead List response. '\n    + 'Raw response: ' + JSON.stringify(response)\n  );\n}\n\nstaticData.leadListId = String(listId);\n\nreturn [{\n  json: {\n    message: \`Lead list created and ID saved: \${listId}. All future runs will add leads to this list automatically.\`,\n    leadListId: listId\n  }\n}];\n"
          },
          "id": "save-list-id",
          "name": "Save List ID",
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [1550, -150],
          "notes": "Extracts lead_list_id from the Create Lead List response and persists it in workflow static data."
        },
        {
          "parameters": {
            "mode": "runOnceForAllItems",
            "jsCode": "// ─── Read lead list ID directly from static data ───\n// This avoids relying on json passing through IF nodes\nconst staticData = $getWorkflowStaticData('global');\nconst leadListId = staticData.leadListId || '';\n\nif (!leadListId) {\n  throw new Error(\n    'No leadListId found in workflow static data. '\n    + 'The first run may not have saved it correctly. '\n    + 'Static data contents: ' + JSON.stringify(staticData)\n  );\n}\n\n// Forward the incoming item (with binary CSV) and attach leadListId\nconst items = $input.all();\nitems[0].json.leadListId = leadListId;\nreturn items;\n"
          },
          "id": "read-list-id",
          "name": "Read List ID",
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [1300, 50],
          "notes": "Reads the persisted lead list ID from workflow static data and attaches it to the item for the next HTTP node."
        },
        {
          "parameters": {
            "method": "POST",
            "url": "https://live.outflo.in/api/public/lead-lists/leads",
            "authentication": "genericCredentialType",
            "genericAuthType": "httpHeaderAuth",
            "sendBody": true,
            "contentType": "multipart-form-data",
            "bodyParameters": {
              "parameters": [
                {
                  "parameterType": "formData",
                  "name": "leadListId",
                  "value": "={{ $json.leadListId }}"
                },
                {
                  "parameterType": "formBinaryData",
                  "name": "csv",
                  "inputDataFieldName": "csv"
                }
              ]
            },
            "options": {}
          },
          "id": "outflo-add-to-list",
          "name": "Add Leads to List",
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [1550, 50],
          "credentials": {
            "httpHeaderAuth": {
              "id": "OUTFLO_HEADER_AUTH_CREDENTIAL_ID",
              "name": "Outflo API Key"
            }
          },
          "notes": "Subsequent runs. Adds new leads to the existing list."
        },
        {
          "parameters": {},
          "id": "no-leads-noop",
          "name": "No New Leads",
          "type": "n8n-nodes-base.noOp",
          "typeVersion": 1,
          "position": [1000, 150]
        }
      ],
      "connections": {
        "Poll Every Hour": {
          "main": [
            [
              {
                "node": "Fetch Apollo Contacts",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Fetch Apollo Contacts": {
          "main": [
            [
              {
                "node": "Build CSV",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Build CSV": {
          "main": [
            [
              {
                "node": "Has Leads?",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Has Leads?": {
          "main": [
            [
              {
                "node": "Is First Run?",
                "type": "main",
                "index": 0
              }
            ],
            [
              {
                "node": "No New Leads",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Is First Run?": {
          "main": [
            [
              {
                "node": "Create Lead List",
                "type": "main",
                "index": 0
              }
            ],
            [
              {
                "node": "Read List ID",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Create Lead List": {
          "main": [
            [
              {
                "node": "Save List ID",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Read List ID": {
          "main": [
            [
              {
                "node": "Add Leads to List",
                "type": "main",
                "index": 0
              }
            ]
          ]
        }
      },
      "settings": {
        "executionOrder": "v1"
      },
      "tags": [
        {
          "name": "lead-sync"
        }
      ]
    }
    Share this guide:
    Tushar Singla

    Written by Tushar Singla

    Tushar is the founder of OutFlo, focused on building safe, programmable, and affordable LinkedIn outreach systems for modern GTM teams.

    Ready to transform your LinkedIn outreach?

    Join the growing community of sales professionals and marketers who are revolutionizing their LinkedIn lead generation with OutFlo.