Public API Reference

Enflow Engine API Documentation

Enflow Engine API — Public Reference

The Enflow Engine API lets you enrich companies and people programmatically: get firmographic data for a company from its domain, find email addresses and LinkedIn profiles for people, verify emails, and more — all through a single API key.

  • Base URL (production): https://enflowpy.vercel.app/api/v1
  • Interactive docs (Swagger): https://enflowpy.vercel.app/api/v1/docs
  • OpenAPI schema: https://enflowpy.vercel.app/api/v1/openapi.json

All endpoints return JSON. Requests and responses use Content-Type: application/json.


Quick start

  1. Create an accountPOST /auth/register. The response includes your Enflow Engine API key (shown only once).
  2. Verify your email (recommended) → click the link in the verification email (or POST /auth/verify with the token). You can start using the API immediately, but verifying keeps your account fully activated.
  3. Call any enrichment endpoint with your key in the Api-Key header:
curl -X POST https://enflowpy.vercel.app/api/v1/companies/enrich \
  -H "Content-Type: application/json" \
  -H "Api-Key: ENFLOW_YOUR_API_KEY_HERE" \
  -d '{"domain": "usaa.com"}'

Authentication

Enflow supports two ways to authenticate. Every enrichment and dashboard endpoint accepts either.

Send your API key in the Api-Key header:

Api-Key: ENFLOW_your_api_key_here

Your API key is generated when you register and is only displayed once. If you lose it, regenerate it (see Regenerate API key). Each account has one active key at a time; generating a new one revokes the old.

2. JWT Bearer token (dashboard/UI sessions)

Log in via POST /auth/login to get access and refresh tokens, then send:

Authorization: Bearer <access_token>

Access tokens last 1 hour; refresh tokens last 7 days (use POST /auth/refresh to rotate).


Connect Enflow to n8n

Enflow works with both n8n Cloud and self-hosted n8n through the built-in HTTP Request node. No custom or community node is required.

Prerequisites

  • An Enflow Engine API key
  • An n8n workflow with an HTTP Request node
  • Production base URL: https://enflowpy.vercel.app/api/v1

Store the API key securely

Do not paste the API key directly into every node or put it in a URL/query parameter.

  1. In n8n, open Credentials → New credential.
  2. Choose Header Auth.
  3. Set Name to Api-Key.
  4. Set Value to your Enflow Engine API key.
  5. Save it as Enflow API and select that credential in each Enflow HTTP Request node.

For self-hosted n8n, an environment-backed credential or n8n external secrets integration is also suitable. Never expose the key in workflow exports committed to source control.

Import the ready-made core operations workflow

Download and import enflow-core-operations.workflow.json in n8n → Workflows → Import from File. It contains five independent production webhook flows:

Operation n8n production webhook path Enflow endpoint
People search POST /webhook/enflow/people/search POST /people/search
People enrichment POST /webhook/enflow/people/enrich POST /people/enrich
Company search POST /webhook/enflow/companies/search POST /companies/search
Company enrichment POST /webhook/enflow/companies/enrich POST /companies/enrich
Email verification POST /webhook/enflow/email/verify POST /email/verify

After importing:

  1. Create the Enflow API Header Auth credential as described above.
  2. Open each of the five Enflow ... HTTP Request nodes and select that credential. Imported credential IDs are placeholders and never contain a secret.
  3. Save and activate the workflow.
  4. Replace https://YOUR_N8N_HOST in the examples below with your n8n hostname.

The workflow forwards each webhook JSON body to the matching Enflow endpoint. Its people-search branch includes a Format People Rows Code node that recognizes cleaned_people, people, results, and provider responses nested at raw_data.people, then emits one flat n8n item per person for a readable table. Other branches return Enflow's JSON response unchanged. It uses a 60-second request timeout and does not embed or return the API key.

People search

curl -X POST https://YOUR_N8N_HOST/webhook/enflow/people/search \
  -H "Content-Type: application/json" \
  -d '{
    "companies": {"domains": ["stripe.com"]},
    "people": {"jobTitles": ["Chief Financial Officer"], "countries": ["US"]},
    "limit": 25
  }'

People enrichment — provide an email, LinkedIn URL, or a supported bulk people array:

curl -X POST https://YOUR_N8N_HOST/webhook/enflow/people/enrich \
  -H "Content-Type: application/json" \
  -d '{"linkedin_url":"https://www.linkedin.com/in/example"}'

Company search

curl -X POST https://YOUR_N8N_HOST/webhook/enflow/companies/search \
  -H "Content-Type: application/json" \
  -d '{"filters":{"industries":["Financial Services"],"countries":["US"]},"limit":25}'

Company enrichment — use domain for one company or domains for supported bulk input:

curl -X POST https://YOUR_N8N_HOST/webhook/enflow/companies/enrich \
  -H "Content-Type: application/json" \
  -d '{"domain":"stripe.com"}'

Email verification — use email for one address or emails for supported bulk input:

curl -X POST https://YOUR_N8N_HOST/webhook/enflow/email/verify \
  -H "Content-Type: application/json" \
  -d '{"email":"person@example.com"}'

Protect these public n8n webhooks with n8n authentication, an API gateway, IP restrictions, or another access-control layer appropriate to your deployment. The Enflow credential protects the upstream API but does not authenticate callers to your n8n webhook.

Test the connection

Create an HTTP Request node with:

Setting Value
Method GET
URL https://enflowpy.vercel.app/api/v1/dashboard/stats
Authentication Generic Credential TypeHeader AuthEnflow API
Response Format JSON

A 200 JSON response confirms connectivity and authentication. A 401 response means the Api-Key header is missing, expired, or incorrect.

n8n URL requirement: HTTP Request nodes require an absolute URL beginning with https:// or http://. Values under API discovery's endpoints object are relative paths for compatibility and cannot be used alone. Use the corresponding value under endpoint_urls, or prepend the production origin.

Correct company-search URL:

https://enflowpy.vercel.app/api/v1/companies/search

If an Endpoints node feeds the HTTP Request node, use this expression:

{{ $json.endpoint_urls.enrichment.companies_search }}

For the older relative field, concatenate the origin explicitly:

https://enflowpy.vercel.app{{ $json.endpoints.enrichment.companies_search }}

Example: enrich a company

Configure an HTTP Request node:

Setting Value
Method POST
URL https://enflowpy.vercel.app/api/v1/companies/enrich
Authentication Header AuthEnflow API
Send Headers Content-Type: application/json
Send Body On
Body Content Type JSON
Specify Body Using JSON
{
  "domain": "{{ $json.domain }}"
}

Connect a Manual Trigger, Webhook, spreadsheet, CRM, or database node before it. Each incoming n8n item should contain a domain field. n8n executes the request once per item unless you aggregate several items into a supported bulk payload.

Example: search for people

Use POST https://enflowpy.vercel.app/api/v1/people/search with this JSON body:

{
  "companies": {
    "domains": ["{{ $json.domain }}"]
  },
  "people": {
    "jobTitles": ["Chief Financial Officer"],
    "countries": ["US"]
  },
  "limit": 50
}

Response placement can vary by provider. People may be in cleaned_people, people, results, or raw_data.people. The downloadable workflow handles all four shapes with its Format People Rows node and outputs one flat item per person. For a manually built workflow using the response shown in n8n, add Split Out with Field to Split Out set to raw_data.people, or use the formatter node from the downloadable workflow.

Example: enrich a person

Configure an HTTP Request node:

Setting Value
Method POST
URL https://enflowpy.vercel.app/api/v1/people/enrich
Authentication Header AuthEnflow API
Send Headers Content-Type: application/json
Send Body On
Body Content Type JSON
Specify Body Using JSON

Use an email address:

{
  "email": "{{ $json.email }}",
  "include": {
    "email": true,
    "linkedInUrl": true,
    "mobile": false,
    "jobHistory": false
  }
}

Or use a LinkedIn profile URL:

{
  "linkedin_url": "{{ $json.linkedin_url }}",
  "include": {
    "email": true,
    "linkedInUrl": true
  }
}

For bulk input, aggregate incoming n8n items before the HTTP Request node and send:

{
  "people": {{ JSON.stringify($json.people) }}
}

Each object in people must contain an email or linkedin_url supported by the endpoint. Validate that at least one identifier exists before sending the request. Large bulk requests may return a background-job identifier; follow the polling instructions below instead of creating a duplicate enrichment request.

Example: search for companies

Configure an HTTP Request node:

Setting Value
Method POST
URL https://enflowpy.vercel.app/api/v1/companies/search
Authentication Header AuthEnflow API
Send Headers Content-Type: application/json
Send Body On
Body Content Type JSON
Specify Body Using JSON

Example using industry, country, and employee-count filters:

{
  "filters": {
    "industries": ["Financial Services"],
    "countries": ["US"],
    "employeeCount": {
      "from": 50,
      "to": 5000
    }
  },
  "limit": 25
}

Example using dynamic domains from a previous n8n node:

{
  "filters": {
    "domains": {{ JSON.stringify($json.domains) }}
  },
  "limit": 25
}

Company rows may be returned in companies, results, or raw_data.companies, depending on the selected provider. Add a Split Out node for the returned array to display one company per n8n table row. If nextPageToken is present, use the pagination pattern below.

Example: verify an email

Configure an HTTP Request node with POST https://enflowpy.vercel.app/api/v1/email/verify, the Enflow API Header Auth credential, and this JSON body:

{
  "email": "{{ $json.email }}"
}

For supported bulk input:

{
  "emails": {{ JSON.stringify($json.emails) }}
}

Company search returns normalized domains at companyDomains; some provider responses also include the same array at raw_data.companyDomains. When the Company Search node directly feeds the People Search node, use:

{
  "companies": {
    "domains": {{ JSON.stringify($json.companyDomains || $json.raw_data.companyDomains) }}
  },
  "limit": 5,
  "peoplePerCompany": 5
}

When referencing a node named Company Search from elsewhere in the workflow:

{
  "companies": {
    "domains": {{ JSON.stringify($('Company Search').first().json.companyDomains || $('Company Search').first().json.raw_data.companyDomains) }}
  },
  "limit": 5,
  "peoplePerCompany": 5
}

Before execution, check the expression preview. Correct array output contains brackets and quoted strings:

"domains": ["nuveen.com", "carlyle.com"]

If the preview shows "domains": nuveen.com,carlyle.com, the array was interpolated as text and the body is invalid JSON.

Expressions and dynamic input

Use n8n expressions anywhere in the JSON body. Quote scalar string expressions, but serialize arrays and objects with JSON.stringify(...) when embedding them in Using JSON mode.

Scalar strings:

{
  "email": "{{ $json.email }}",
  "linkedin_url": "{{ $json.linkedin_url }}"
}

When a value may be absent, validate it with an If node before calling Enflow. This avoids preventable 400 Bad Request responses.

Paginated search workflows

People and company searches can return nextPageToken. To request another page, send it back as pageToken with the same filters:

{
  "companies": {
    "domains": ["{{ $('Input').item.json.domain }}"]
  },
  "limit": 50,
  "pageToken": "{{ $json.nextPageToken }}"
}

Use an If node to check whether {{ $json.nextPageToken }} is non-empty, then loop back to the HTTP Request node. Add a page counter or maximum-iteration guard to prevent accidental infinite loops. Aggregate pages only after the token is empty.

Background jobs and polling

Large bulk requests may return an enrichmentID with status: processing instead of final rows. In that case:

  1. Add a Wait node (for example, 5–15 seconds).
  2. Call GET https://enflowpy.vercel.app/api/v1/dashboard/jobs/{{ $json.enrichmentID }}/status.
  3. If status is PENDING, PROCESSING, or PAUSED, wait and poll again.
  4. When status is COMPLETED, call GET https://enflowpy.vercel.app/api/v1/dashboard/jobs/{{ $json.enrichmentID }}?page=1&limit=100.
  5. If more rows exist, increment page until all results are collected.
  6. Route FAILED or CANCELLED to an error-handling branch.

Set a maximum poll count and timeout appropriate to the workflow. Do not poll continuously without a Wait node.

Error handling in n8n

For production workflows, enable Retry on Fail only for transient responses:

Status Recommended handling
400 Fix or validate the request body; do not retry unchanged input.
401 Check the stored Enflow credential.
402 / 429 Wait, review provider quota, or retry later with backoff.
503 Retry with exponential backoff; provider availability may be temporary.
Network timeout Retry with a bounded attempt count and increasing delay.

Use an Error Trigger workflow or the node's error output to record the original item, HTTP status, and response body. Avoid enabling unlimited retries because provider operations can consume quota.

Trigger/Webhook
  → Validate required fields
  → HTTP Request (Enflow)
  → If background job: Wait + poll
  → Split Out result rows
  → Upsert into CRM/database
  → Error branch / dead-letter storage

Keep one Enflow credential per environment, use a separate workflow or environment variable for the base URL, and avoid logging API keys or full sensitive person records.


AI access via MCP

Important: https://enflowpy.vercel.app/api/v1 is a REST/OpenAPI base URL, not a remote MCP or OAuth endpoint. Do not paste it into an MCP URL connector. Enflow's MCP integration is a local stdio bridge: the MCP client must launch mcp/server.py, which then calls the REST API securely with your server-side API key.

Enflow ships an official MCP (Model Context Protocol) server so AI assistants like Claude Desktop and Cursor can call the Enflow Engine API directly as tools. You talk to the AI in plain language ("enrich usaa.com and find their marketing leads") and it performs the API calls for you.

What you get

The MCP server exposes 30 tools covering every section of this API:

Group Tools
Company enrich_company, enrich_companies_bulk, search_companies, company_lookalike
People enrich_person, enrich_people_bulk, search_people
Verification & intel verify_email, verify_emails_bulk, website_status, website_social, website_people, iab_classify
AI operations ai_company_intel, ai_lead_qualification, ai_content_extraction
Google Places google_places_text_search, google_places_nearby_search, google_places_place_details
Reference data reference_countries, reference_industries, reference_departments, reference_seniorities, reference_job_titles
Jobs get_job_status, list_jobs

Tools use your Enflow Engine API key and hit the same production REST base URL, so credits and limits apply exactly as documented above. The bridge reads the key from ENFLOW_API_KEY and sends it using the OpenAPI security scheme Api-Key: <key>. Keep this variable in the server/MCP process environment; never place it in browser code or client bundles.

The bridge does not automatically retry enrichment requests. If a request times out, its completion state may be unknown; do not blindly repeat it.

Prerequisites

  • Python 3.10+
  • Your Enflow Engine API key (from registration — see Regenerate API key if you lost it)
  • The server files from the mcp/ directory of the Enflow repository (server.py, requirements.txt)

Quick start

# 1. Install dependencies
pip install -r mcp/requirements.txt

# 2. Set your API key
export ENFLOW_API_KEY="enflow_your_key_here"

# 3. Run the server (stdio) — or skip this: your AI client launches it for you
python mcp/server.py

Connect Claude Desktop

Open Claude Desktop → Settings → Developer → Edit Config and add to claude_desktop_config.json:

{
  "mcpServers": {
    "enflow": {
      "command": "python",
      "args": ["/absolute/path/to/mcp/server.py"],
      "env": {
        "ENFLOW_API_KEY": "enflow_your_key_here"
      }
    }
  }
}

Restart Claude Desktop. The Enflow tools appear automatically — try: "Use Enflow to enrich usaa.com and verify the CEO's email."

Connect Cursor

In Cursor → Settings → MCP Servers, add a new server:

  • Name: enflow
  • Type: command
  • Command: python /absolute/path/to/mcp/server.py
  • Environment variables: ENFLOW_API_KEY = your key

Environment variables

Variable Default Description
ENFLOW_API_KEY (Required) Your Enflow Engine API key
ENFLOW_API_URL https://enflowpy.vercel.app/api/v1 REST API base URL (not an MCP URL; override for staging)
RUN_ENFLOW_SMOKE_TEST false Must be true to permit the live Stripe smoke test

Testing the server

cd mcp
ENFLOW_API_KEY="your_key" python -m mcp dev server.py

This launches the MCP Inspector so you can verify initialization, tool discovery, and tool calls before wiring the server into your AI client.

For an explicitly enabled live company-enrichment smoke test:

cd mcp
ENFLOW_API_KEY="your_key" RUN_ENFLOW_SMOKE_TEST=true python smoke_test.py

The smoke test enriches stripe.com and prints only non-sensitive fields. Normal unit tests mock all HTTP requests and never consume enrichment credits.

Authentication troubleshooting

  • HTTP 401 means the REST service was reached but the key was missing or invalid. Confirm the server process has ENFLOW_API_KEY and sends it as the Api-Key header.
  • An OAuth or JSON-RPC transport error during MCP initialization usually means the REST URL was incorrectly configured as an MCP server. Configure a local command that launches mcp/server.py instead.
  • HTTP 429 means the request was rate-limited. Wait before making a deliberate retry.
  • HTTP 500 or 503 means the service or its providers are temporarily unavailable. Do not expose credentials while collecting diagnostics.

1. Account & registration

Register a user

Creates an account and returns your API key.

POST /api/v1/auth/register

Request body

Field Type Required Description
email string Account email (used for login).
password string Password (min 8 chars recommended).
organization_name string Your company/organization name.
tier string free (default), starter, explorer, or pro.

Example request

curl -X POST https://enflowpy.vercel.app/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@company.com",
    "password": "sup3r-secret-password",
    "organization_name": "Your Company",
    "tier": "free"
  }'

Example response — 200 OK

{
  "user_id": 42,
  "email": "you@company.com",
  "api_key": "ENFLOW_7f9c...your_key...",
  "message": "Registration successful. Please check your email to verify your account."
}

⚠️ Save the api_key immediately — it is not returned again.

Errors

Status code Meaning
400 USER_EXISTS An account with that email exists.

Verify email

POST /api/v1/auth/verify
{ "token": "verification-token-from-email" }

200{"message": "Email verified successfully"}. 400 → invalid/expired token.

Resend verification email

POST /api/v1/auth/resend-verification
{ "email": "you@company.com" }

Always returns {"message": "Verification email sent"} (does not reveal whether the account exists).

Login (JWT)

POST /api/v1/auth/login
{ "email": "you@company.com", "password": "sup3r-secret-password" }

Response — 200 OK

{
  "access": "eyJhbGciOiJIUzI1NiIs...",
  "refresh": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": 42,
    "email": "you@company.com",
    "role": "member",
    "tier": "free",
    "is_verified": true
  }
}

Refresh access token

POST /api/v1/auth/refresh
{ "refresh": "eyJhbGciOiJIUzI1NiIs..." }

Returns a fresh access token (and a rotated refresh token).

Password reset

POST /api/v1/auth/password-reset/request   →  { "email": "you@company.com" }
POST /api/v1/auth/password-reset/confirm   →  { "uid": "...", "token": "...", "new_password": "..." }

Get profile

GET /api/v1/profile/

Auth: Api-Key or JWT Bearer. Returns your user info, tier, credits used, and the prefix of your active API key (the full key is only shown once).

Regenerate API key

POST /api/v1/profile/regenerate-key

Auth: JWT Bearer only. Revokes the current key and returns a new one (shown once):

{
  "message": "API key regenerated successfully",
  "api_key": "ENFLOW_new_key_here",
  "warning": "Save this key now. You won't be able to see it again."
}

Redeem a platform code (AppSumo / lifetime deals)

POST /api/v1/profile/redeem
{ "code": "YOUR-CODE" }

Unlocks a tier and its monthly credits.


2. Company enrichment

Enrich a company — single

POST /api/v1/companies/enrich

Request body — provide domain or website:

{ "domain": "usaa.com" }

Example response — 200 OK

{
  "operation": "company_enrichment",
  "total": 1,
  "successful": 1,
  "failed": 0,
  "results": [
    {
      "success": true,
      "result": {
        "name": "USAA Property and Casualty Insurance Group",
        "domain": "usaa.com",
        "website": "https://usaa.com",
        "industry": "Retirement",
        "employee_count": 4102,
        "revenue": "$500–1000M",
        "founded": 1922,
        "hq_address": "...",
        "linkedin_url": "https://linkedin.com/company/usaa",
        "description": "...",
        "keywords": ["auto insurance", "banking", "..."]
      },
      "input": { "domain": "usaa.com" },
      "source": "Surfe"
    }
  ]
}

Enrich companies — bulk

POST /api/v1/companies/enrich
{ "domains": ["usaa.com", "apple.com", "stripe.com"] }

Response is the same shape with one results[] entry per input domain (order preserved; failed domains have "success": false with an error). Up to 50 domains run synchronously and return inline; larger bulk requests create a background job (see Jobs).

Find companies in the provider's database using firmographic filters.

POST /api/v1/companies/search

Request bodyfilters is required (at least one filter). limit is optional (default 50, max 200).

{
  "filters": {
    "industries": ["Fintech", "Banking"],
    "countries": ["US", "GB"],
    "employeeCount": { "from": 100, "to": 5000 },
    "revenue": { "from": 10000000, "to": 1000000000 }
  },
  "limit": 50
}

Filter reference (Surfe v2 schema)

Filter Type Description
industries string[] Industry names.
countries string[] ISO country codes (US, GB, …). Full country names are also accepted and auto-converted.
names string[] Exact company names.
domains string[] Company domains (e.g., ["stripe.com"]).
domainsExcluded string[] Exclude these domains.
employeeCount {from, to} Headcount range.
revenue {from, to} Annual revenue range (USD).

⚠️ filters is passed through to whichever external provider is configured for your plan. The keys above follow the Surfe v2 schema; filter names for other providers (e.g., Apollo) may differ.

Example response — 200 OK (provider-native shape, not the enrichment envelope)

{
  "companies": [
    {
      "name": "Stripe",
      "domain": "stripe.com",
      "website": "https://stripe.com",
      "industries": ["Fintech", "Payments"],
      "employeeCount": 8000,
      "revenue": 10000000000,
      "countries": ["US", "IE"]
    }
  ],
  "companyDomains": ["stripe.com"],
  "total": 1,
  "nextPageToken": "...",
  "pageToken": "",
  "limit": 50
}

Pagination — pass the returned nextPageToken as pageToken on the next request to fetch the following page:

{ "filters": { "industries": ["Fintech"] }, "limit": 50, "pageToken": "..." }

Code example

curl -X POST https://enflowpy.vercel.app/api/v1/companies/search \
  -H "Content-Type: application/json" \
  -H "Api-Key: ENFLOW_your_api_key_here" \
  -d '{"filters": {"industries": ["Fintech"], "countries": ["US"]}, "limit": 20}'

Company lookalike

POST /api/v1/companies/lookalike
{
  "domain": "stripe.com",
  "filters": { "industries": ["Fintech"] }
}

Returns similar companies to the reference domain.


3. People enrichment

Enrich a person — by LinkedIn URL or email

POST /api/v1/people/enrich
{ "linkedin_url": "https://www.linkedin.com/in/jane-doe" }

or

{ "email": "jane.doe@company.com" }

Example response — 200 OK

{
  "operation": "people_enrichment",
  "total": 1,
  "successful": 1,
  "failed": 0,
  "results": [
    {
      "success": true,
      "result": {
        "full_name": "Jane Doe",
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "jane.doe@company.com",
        "job_title": "VP of Marketing",
        "company_name": "Acme Inc",
        "company_domain": "acme.com",
        "linkedin_url": "https://www.linkedin.com/in/jane-doe",
        "phone": "+1 555-0100"
      },
      "input": { "linkedin_url": "https://www.linkedin.com/in/jane-doe" },
      "source": "Surfe"
    }
  ]
}

You can also pass "include" to control which fields are fetched:

{ "linkedin_url": "...", "include": { "email": true, "jobHistory": true, "linkedInUrl": true, "mobile": true } }

Enrich people — bulk

POST /api/v1/people/enrich
{
  "people": [
    { "linkedinUrl": "https://www.linkedin.com/in/jane-doe" },
    { "email": "john@acme.com" }
  ]
}

Up to 100 people run synchronously; larger payloads create a background job (max 10,000 per request).

Find people (contacts) using company and/or person filters.

POST /api/v1/people/search

Request body — at least one of companies or people is required. limit is optional (default 50, max 200).

{
  "companies": {
    "domains": ["usaa.com", "stripe.com"],
    "employeeCount": { "from": 100, "to": 10000 }
  },
  "people": {
    "jobTitles": ["Manager", "Director"],
    "countries": ["US"],
    "departments": ["Engineering", "Marketing"],
    "seniorities": ["Manager", "Director"]
  },
  "limit": 50,
  "peoplePerCompany": 5
}

Filter reference (Surfe v2 schema)

Group Filter Type Description
companies domains string[] Target company domains.
companies countries string[] ISO country codes (names auto-converted).
companies domainsExcluded string[] Exclude these domains.
companies employeeCount {from, to} Company headcount range.
companies industries string[] Company industries.
companies names string[] Company names.
companies revenue {from, to} Company annual revenue range.
people countries string[] Person's country (ISO codes).
people departments string[] Departments (Engineering, Sales, …).
people jobTitles string[] Job titles.
people seniorities string[] Seniority levels.

⚠️ Filter keys follow the configured external provider's schema (Surfe v2 shown above).

Example response — 200 OK (provider-native shape, not the enrichment envelope)

{
  "people": [
    {
      "firstName": "Jane",
      "lastName": "Doe",
      "jobTitle": "VP of Marketing",
      "companyName": "USAA",
      "companyDomain": "usaa.com",
      "linkedInUrl": "https://www.linkedin.com/in/jane-doe",
      "country": "US",
      "seniorities": ["VP"],
      "departments": ["Marketing"]
    }
  ],
  "total": 1,
  "nextPageToken": "...",
  "pageToken": "",
  "limit": 50
}

Pagination — pass nextPageToken as pageToken on the next request. Optionally cap people per company with peoplePerCompany. For Surfe searches with multiple companies.domains and a peoplePerCompany cap, Enflow queries each domain independently, merges and deduplicates the people, and applies limit globally so one domain cannot consume the entire result set.

Code example

curl -X POST https://enflowpy.vercel.app/api/v1/people/search \
  -H "Content-Type: application/json" \
  -H "Api-Key: ENFLOW_your_api_key_here" \
  -d '{"companies": {"domains": ["usaa.com"]}, "people": {"jobTitles": ["CTO"]}, "limit": 20}'

4. Verification, website & intelligence

Email verification

POST /api/v1/email/verify
{ "email": "jane.doe@company.com" }

Bulk: { "emails": ["a@x.com", "b@y.com"] }

Website status

POST /api/v1/website-status
{ "domain": "usaa.com" }

Resolves the domain and returns the final URL details needed for downstream enrichment. Bulk: { "domains": ["a.com", "b.com"] } (creates a job).

POST /api/v1/website-social/enrich
{ "domain": "usaa.com" }

Scrapes the site and returns discovered social URLs (LinkedIn, Twitter/X, Facebook, Instagram, …). Bulk: { "domains": [...] } — up to 20 domains sync.

Website people (people on a site)

POST /api/v1/website-people/enrich
{
  "domain": "usaa.com",
  "max_pages": 8,
  "max_people": 50,
  "execution_mode": "fast"
}

Crawls the site and extracts people (names, titles, emails, LinkedIn). Bulk: { "domains": [...] } — up to 10 domains sync.

IAB classification

POST /api/v1/iab/classify
{
  "company_name": "Stripe",
  "company_website": "stripe.com",
  "company_description": "Payment processing platform"
}

Response

{
  "primary_category": "Finance",
  "secondary_categories": ["Payments", "Fintech"],
  "confidence_score": 0.95,
  "iab_categories": { "IAB17": "Finance" }
}

Google Places

POST /api/v1/google-places/text-search      →  { "query": "coffee shops", "location": "Austin, TX", "result_limit": 20 }
POST /api/v1/google-places/nearby-search    →  { "latitude": 30.26, "longitude": -97.74, "radius": 5000, "page_size": 10 }
POST /api/v1/google-places/place-details    →  { "place_id": "ChIJ..." }

AI operations

POST /api/v1/ai/company-intel          →  { "content": "stripe.com" }  — AI company intelligence report
POST /api/v1/ai/lead-qualification     →  { "content": "...text..." }  — AI lead scoring
POST /api/v1/ai/content-extraction     →  { "content": "...text..." }  — AI structured extraction

All three accept optional "model" and "provider_id".


Response shapes for searches, verification, and website-status are returned as-is from the configured provider (not wrapped in the {operation, total, results[]} enrichment envelope). Enrichment endpoints (/companies/enrich, /people/enrich, /website-social/enrich, /website-people/enrich) always use the envelope.


5. Jobs & dashboard

Bulk operations above the synchronous caps return a job that runs in the background:

{
  "enrichmentID": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "processing",
  "total": 120,
  "message": "Bulk enrichment job created for 120 companies"
}
Endpoint Method Description
/dashboard/jobs GET List your jobs from the last 7 days.
/dashboard/jobs/{id} GET Full job detail incl. paginated results (page, limit).
/dashboard/jobs/{id}/status GET Lightweight status for polling.
/dashboard/jobs/{id}/cancel POST Cancel a PENDING / PROCESSING / PAUSED job.
/dashboard/jobs/{id}/companies GET Enriched company rows for a job.
/dashboard/jobs/{id} DELETE Delete a COMPLETED / FAILED / CANCELLED job.
/dashboard/stats GET Usage stats: credits used, success rate, job counts.

Job statuses: PENDINGPROCESSINGCOMPLETED (or FAILED / CANCELLED).


Error responses

Non-2xx responses return JSON with a detail (and often a machine-readable code):

{ "detail": "No active provider key available. Please add your own API key in Settings.", "code": "NO_PROVIDER_AVAILABLE" }
Status Common causes
400 Bad request, missing/invalid input, no provider available for the operation
401 Missing/invalid Api-Key header or JWT token
404 Requested resource not found
503 All providers exhausted (ALL_PROVIDERS_EXHAUSTED) or database overloaded (DATABASE_OVERLOADED)

Credits, tiers & limits

  • Every successful result consumes credits from your monthly plan (e.g., enrichment = 2 credits/result, search = 1/result).
  • Tiers: free (50 credits/mo), starter (500), explorer (2000), pro (10,000).
  • Operations use your highest-priority eligible provider key and rotate according to provider priority. Add your own provider API keys in the Enflow dashboard to raise your limits.

Code examples

cURL

# Register
curl -X POST https://enflowpy.vercel.app/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@company.com","password":"sup3r-secret","organization_name":"Acme"}'

# Enrich a company
curl -X POST https://enflowpy.vercel.app/api/v1/companies/enrich \
  -H "Content-Type: application/json" \
  -H "Api-Key: ENFLOW_your_api_key_here" \
  -d '{"domain":"usaa.com"}'

# Enrich a person
curl -X POST https://enflowpy.vercel.app/api/v1/people/enrich \
  -H "Content-Type: application/json" \
  -H "Api-Key: ENFLOW_your_api_key_here" \
  -d '{"linkedin_url":"https://www.linkedin.com/in/jane-doe"}'

Python (requests)

import requests

BASE = "https://enflowpy.vercel.app/api/v1"
HEADERS = {"Api-Key": "ENFLOW_your_api_key_here", "Content-Type": "application/json"}

# Register (returns your API key)
r = requests.post(f"{BASE}/auth/register", json={
    "email": "you@company.com",
    "password": "sup3r-secret",
    "organization_name": "Acme",
})
print(r.json()["api_key"])  # ⚠️ save this — shown only once

# Enrich a company
r = requests.post(f"{BASE}/companies/enrich", json={"domain": "usaa.com"}, headers=HEADERS)
data = r.json()
company = data["results"][0]["result"]
print(company["name"], company["industry"], company["employee_count"])

# Enrich people in bulk
r = requests.post(f"{BASE}/people/enrich", json={
    "people": [
        {"linkedinUrl": "https://www.linkedin.com/in/jane-doe"},
        {"email": "john@acme.com"},
    ]
}, headers=HEADERS)
for row in r.json()["results"]:
    print(row["success"], row.get("result", {}).get("email"), row.get("error"))

JavaScript / Node.js

const BASE = "https://enflowpy.vercel.app/api/v1";

async function enrichCompany(domain, apiKey) {
  const res = await fetch(`${BASE}/companies/enrich`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Api-Key": apiKey,
    },
    body: JSON.stringify({ domain }),
  });
  const json = await res.json();
  return json.results?.[0]?.result ?? json;
}

enrichCompany("usaa.com", "ENFLOW_your_api_key_here").then(console.log);

Rate limiting & fair use

  • Respect your tier's monthly credits and provider daily quotas (visible in the dashboard).
  • Bulk requests above the sync caps (50 companies / 100 people / 20 website-social / 10 website-people) are processed as background jobs — poll GET /dashboard/jobs/{id}/status for completion.

Need support? Contact the Enflow team at the address shown on the Enflow website.