{
  "openapi": "3.0.1",
  "info": {
    "title": "Email List Cleaner: Bulk Email Verify, Keep Qualified Emails",
    "description": "First 25 qualified emails free. Email list cleaner: check email deliverability (MX, disposable, role, typo), score ICP fit with AI and bill only qualified emails (≈ $1.10 per 1,000 cleaned). Works on any plan; the optional deep SMTP check needs a plan that runs Store Actors.",
    "version": "0.1",
    "x-build-id": "VwgcffFHZennTxUvV"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/rich_minds~email-list-qualifier-ai/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-rich_minds-email-list-qualifier-ai",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/rich_minds~email-list-qualifier-ai/runs": {
      "post": {
        "operationId": "runs-sync-rich_minds-email-list-qualifier-ai",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/rich_minds~email-list-qualifier-ai/run-sync": {
      "post": {
        "operationId": "run-sync-rich_minds-email-list-qualifier-ai",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "emails": {
            "title": "Emails to qualify",
            "type": "array",
            "description": "Paste the addresses to check, one per line (anything that looks like an address is picked up). They are checked inside this Actor on any Apify plan, with no second bill; junk is dropped for free, you pay only for qualified ones. One address = one verdict in OUTPUT.verdict. Empty = free demo on 7 sample rows.",
            "items": {
              "type": "string"
            }
          },
          "csvUrl": {
            "title": "Or your list as a CSV / Google Sheet link",
            "type": "string",
            "description": "A link to your list: a CSV file or a Google Sheet shared as 'anyone with the link'. The address column is found for you; other columns stay on the row. Runs on any Apify plan. Affects what you pay: each qualified email of the file is billed here. Cleaned lists come back as sendable.csv, risky.csv and bad.csv."
          },
          "icpDescription": {
            "title": "Who is your ideal recipient? (ICP)",
            "type": "string",
            "description": "Describe the person you want to reach in plain words (role, company type, size). The AI scores every verified address against it (fit 0–100) and explains why from the fields you supplied. Empty = rank by the free rules only."
          },
          "preset": {
            "title": "Start from a ready niche search (one click)",
            "enum": [
              "custom",
              "cold-email-agency",
              "saas-sdr",
              "scraped-leads-cleanup",
              "newsletter-cleanup",
              "event-attendee-list",
              "deep-smtp-check"
            ],
            "type": "string",
            "description": "Pick a niche and press Start: it fills the ICP and the qualification rules you left empty — then paste your emails. Your own values always win. <b>Custom</b> = fill the fields yourself.",
            "default": "custom"
          },
          "minScore": {
            "title": "Minimum score (0–100)",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "The field that decides what you pay: emails whose fit score (AI on) or rule score (AI off) is below this are dropped and <b>not charged</b>. Raise it to pay only for the strongest matches, lower it to see more.",
            "default": 50
          },
          "maxQualified": {
            "title": "Max qualified emails to output",
            "minimum": 1,
            "maximum": 10000,
            "type": "integer",
            "description": "Hard cap on results — and the ceiling of what <b>this Actor</b> charges: at most this many × $0.04 with AI on (× $0.002 with AI off), e.g. 100 × $0.04. Affects what you pay. Best-scoring emails first.",
            "default": 100
          },
          "maxDiscoveryChargeUsd": {
            "title": "Max source spend (USD)",
            "minimum": 0.01,
            "maximum": 1000,
            "type": "number",
            "description": "Only when <b>Built-in verification</b> is off: the hard cap on what BounceVerify may charge your Apify account (≈ $0.00089 per address) — $0.50 in the form and for an API call that leaves it out. Raise it for bigger lists. Ignored while the built-in check runs (no second bill).",
            "default": 0.5
          },
          "enableAi": {
            "title": "AI assessment",
            "type": "boolean",
            "description": "Score each qualified email against your goal, explain why, summarise it and generate text. <b>On = $0.04 per qualified email + AI tokens; off = $0.002</b> (rule-based flags and scores only). Your first 5 qualified emails get live AI verdicts on any plan; every one with Apify model access or a paid own key.",
            "default": true
          },
          "notifyEmail": {
            "title": "E-mail me the new qualified emails (optional)",
            "type": "string",
            "description": "After every run with at least one new qualified email, the digest (how many are new, the top 10, the trend) is e-mailed here through Apify's send-mail Actor. Schedule the run weekly and only new emails reach your inbox. Never sent for the free demo."
          },
          "minVerifierScore": {
            "title": "Minimum verifier score (0–100)",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "The verifier's own deliverability score. Addresses below it are dropped for free. The built-in check scores a clean domain 80 (a role mailbox 70); BounceVerify's mailbox-confirmed rows score 85–95, so raise it to 85 only with the deep check on. 70 keeps clearly safe addresses.",
            "default": 70
          },
          "allowRoleEmails": {
            "title": "Keep role addresses (info@, sales@)",
            "type": "boolean",
            "description": "Off (default): shared mailboxes like info@ or sales@ are dropped for free. On: they are kept and flagged <code>role_based</code>.",
            "default": false
          },
          "allowCatchAll": {
            "title": "Keep catch-all domains",
            "type": "boolean",
            "description": "Off (default): domains that accept every address (deliverability cannot be proven) are dropped for free. On: they are kept, flagged <code>catch_all</code> and never dispositioned as plain <i>send</i>.",
            "default": false
          },
          "allowFreeProviders": {
            "title": "Keep free webmail (Gmail, Yahoo …)",
            "type": "boolean",
            "description": "On (default): free-provider addresses are kept and flagged <code>free_provider</code>. Off: only company-domain addresses qualify.",
            "default": true
          },
          "allowDomains": {
            "title": "Only these domains",
            "type": "array",
            "description": "Keep only addresses on these domains (subdomains match too), e.g. <code>acme.com</code>. Everything else is dropped for free. Empty = all domains.",
            "items": {
              "type": "string"
            }
          },
          "denyDomains": {
            "title": "Never these domains",
            "type": "array",
            "description": "Drop addresses on these domains (competitors, existing customers) for free — subdomains match too.",
            "items": {
              "type": "string"
            }
          },
          "fixTypos": {
            "title": "Fix typos (gmial.com → gmail.com)",
            "type": "boolean",
            "description": "On (default): a probable typo (gmial.com, hotmial.com) is corrected and the corrected address verified once more, then kept with <code>correctedFrom</code> set (with the deep check this is a small extra source charge). Off: typos are dropped.",
            "default": true
          },
          "targetFlags": {
            "title": "Target flags (any of)",
            "type": "array",
            "description": "Only deliver emails that have at least one of these flags, and rank those with more of them higher. Leave empty to keep everything. An API call passes the flag names from the README's flag table, e.g. <code>personal_address</code> or <code>business_domain</code>; an unknown name stops the run at once with the list of valid ones.",
            "items": {
              "type": "string",
              "enum": [
                "personal_address",
                "business_domain",
                "has_context",
                "typo_corrected",
                "role_based",
                "catch_all",
                "free_provider",
                "low_verifier_score"
              ],
              "enumTitles": [
                "Personal address (first.last, not a shared mailbox)",
                "Company domain (not free webmail)",
                "Your row carries company / title / website",
                "Typo corrected and re-verified (gmial.com → gmail.com)",
                "Role mailbox (info@, sales@)",
                "Catch-all domain",
                "Free webmail (Gmail, Yahoo …)",
                "Verifier score below 80"
              ]
            }
          },
          "suppressionList": {
            "title": "Exclude list",
            "type": "array",
            "description": "Addresses or domains to never output (e.g. <code>ceo@acme.com</code>, <code>acme.com</code>). Skipped for free.",
            "items": {
              "type": "string"
            }
          },
          "llmProvider": {
            "title": "AI model access",
            "enum": [
              "apify",
              "byok"
            ],
            "type": "string",
            "description": "<b>Apify (no keys)</b> — the AI runs through Apify's built-in OpenRouter proxy; tokens are billed to your Apify account at OpenRouter's rates. <b>My own key</b> — use your OpenAI / Anthropic / Gemini / Groq key instead; a free-tier key assesses only a few qualified emails per run — expect 1–3 AI checks (2 calls at a time, one after a rate limit, then the 3 borderline qualified emails retried one by one) — and the rest keep their rule score; a paid Groq / Gemini key (billing on) assesses every qualified email.",
            "default": "apify"
          },
          "llmModel": {
            "title": "AI model",
            "type": "string",
            "description": "Leave empty for the default (<code>anthropic/claude-haiku-4.5</code> with Apify, <code>google:gemini-3.5-flash-lite</code> with your own key). Apify mode takes an OpenRouter slug such as <code>openai/gpt-4.1-mini</code>; own-key mode takes <code>provider:model</code>, e.g. <code>anthropic:claude-haiku-4-5-20251001</code>."
          },
          "llmApiKey": {
            "title": "Your API key (own-key mode only)",
            "type": "string",
            "description": "Required when <b>AI model access</b> is <i>My own API key</i>. Stored encrypted by Apify, never logged."
          },
          "sendAlertsTo": {
            "title": "Send alerts to (Slack, Discord, Teams, Google Chat, Telegram or webhook)",
            "type": "string",
            "description": "Paste one or more URLs, one per line — the service is detected from the address: a Slack or Discord channel webhook, a Teams Workflows or Google Chat space webhook, your Telegram bot's send link (<code>https://api.telegram.org/bot&lt;token&gt;/sendMessage?chat_id=@yourchannel</code>) or any other https URL (Zapier, Make, n8n, your CRM), which gets the qualified emails as JSON. After every run with at least one new qualified email, the digest (how many are new, the top 10 with links) is posted there. Never sent for the free demo. Headers and signing: <i>Delivery details (advanced)</i>."
          },
          "dedupeAcrossRuns": {
            "title": "Never output the same qualified email twice",
            "type": "boolean",
            "description": "Remembers every qualified email you were charged for (in a named key-value store on your account) and skips it in future runs.",
            "default": true
          },
          "dedupeTtlDays": {
            "title": "Deliver a qualified email again after (days)",
            "minimum": 0,
            "maximum": 3650,
            "type": "integer",
            "description": "How long a delivered qualified email stays remembered. 0 = never deliver or charge it again. A number of days lets a qualified email that comes back (a seasonal peak, a relisted item) reach you again after that many days — charged again as a new qualified email.",
            "default": 0
          },
          "historyDatasetName": {
            "title": "Keep a running log (dataset name)",
            "type": "string",
            "description": "Optional. Type a name (e.g. <code>email-list-qualifier-ai-seen-log</code>) and every run appends its new qualified emails with the date to that named dataset on your account — one list across months to connect to Google Sheets or your BI tool once. Empty = off. Costs nothing extra.",
            "default": ""
          },
          "sourceMode": {
            "title": "Where do the qualified emails come from?",
            "enum": [
              "actor",
              "dataset",
              "list"
            ],
            "type": "string",
            "description": "<b>actor</b> — verify the pasted <b>Emails to qualify</b> (or the <b>CSV link</b>) inside this Actor by default, on any plan. <b>dataset</b> — an existing Apify dataset. <b>list</b> — rows you paste in. <b>Bring your own data:</b> rows or a dataset ID that already carry a verifier's result, and you pay only for the analysis. The mode you choose always runs as chosen; only the untouched sample records next to emails you typed switch to the live search. <code>{}</code> over the API runs the free demo — nothing charged.",
            "default": "actor"
          },
          "ownSource": {
            "title": "Built-in verification (any plan, no second bill)",
            "type": "boolean",
            "description": "On (default): the addresses are verified inside this Actor — syntax, domain and mail server (MX), disposable domains, role mailboxes, typos — on any Apify plan, with no second bill. It does not probe the mailbox, so catch-all and \"does this mailbox exist\" stay unknown (<code>smtpValid</code> is null). Off: BounceVerify's deep SMTP check runs on your account (≈ $0.00089 per address on top of ours; needs a plan that runs Store Actors).",
            "default": true
          },
          "datasetId": {
            "title": "Dataset",
            "type": "string",
            "description": "Only for <b>dataset</b> mode: a dataset of BounceVerify results, or of any scraper with an e-mail column (those addresses are verified first). Pick it so the Actor is granted read access."
          },
          "itemsList": {
            "title": "Rows to qualify",
            "type": "array",
            "description": "Only for <b>list</b> mode. JSON array of objects with an <code>email</code> (or the column named in <b>E-mail column</b>). Rows that carry BounceVerify's fields (<code>status</code>, <code>score</code>, <code>smtp_valid</code>, <code>is_catch_all</code>, <code>is_disposable</code>, <code>is_role_based</code>, <code>is_free_provider</code>) are used as they are — no source run, you pay only for the analysis; rows with just an address are verified first. Extra fields (company, title …) become context for the AI."
          },
          "discoveryInput": {
            "title": "Source Actor input",
            "type": "object",
            "description": "Only for the deep check (advanced). Raw input for BounceVerify (<code>bounceverify/bounceverify-email-verifier</code>), merged under the Actor's own defaults (<code>skipInvalidSyntax</code>, <code>concurrency</code> 50). Setting it runs the deep check instead of the built-in one. Use <code>inputDatasetId</code> + <code>emailField</code> to verify a dataset in one source run. It runs a Store Actor on your Apify plan — if your plan cannot, use the built-in check, or paste rows BounceVerify already verified in <b>list</b> mode (any plan)."
          },
          "estimateOnly": {
            "title": "Estimate the bill only (free)",
            "type": "boolean",
            "description": "On: the list is counted and the bill printed (worst case and typical, for AI on and off) in the status and <code>OUTPUT.estimate</code> — nothing is verified, charged or remembered. Use it before a big list. Off (default): the run verifies and delivers.",
            "default": false
          },
          "webhookUrl": {
            "title": "Webhook URL (optional)",
            "type": "string",
            "description": "Qualified emails are POSTed here as JSON (Zapier, Make, n8n, your CRM). For Google Sheets / Slack you can also use Apify's built-in Integrations tab. Stored encrypted — Zapier / Make hook URLs carry their secret in the path."
          },
          "webhookHeaders": {
            "title": "Webhook headers (optional)",
            "type": "object",
            "description": "Extra HTTP headers for the webhook, e.g. <code>{\"Authorization\": \"Bearer …\"}</code>. Stored encrypted by Apify, never logged."
          },
          "webhookSecret": {
            "title": "Webhook signing secret (optional)",
            "type": "string",
            "description": "Any random string. Every webhook POST then carries <code>X-Signature: sha256=&lt;HMAC-SHA256 of the raw body with this secret&gt;</code>, so your endpoint can verify it came from this Actor. Stored encrypted by Apify, never logged."
          },
          "dedupeStoreName": {
            "title": "Dedupe store name",
            "type": "string",
            "description": "Key-value store used for cross-run memory. Left at the default, the memory is kept per search (source and search terms): rewording <code>goalDescription</code> keeps it, so a scheduled run never re-delivers what you already have, and another search starts its own list. Running it for several clients on one account, type one name per client.",
            "default": "email-list-qualifier-ai-seen"
          },
          "discoveryTimeoutSecs": {
            "title": "Source run timeout, seconds (API only)",
            "minimum": 60,
            "maximum": 86400,
            "type": "integer",
            "description": "API only — hidden on the form. How long the deep-check source run (BounceVerify) may run before its partial results are used. Raise it when the status says the source stopped early.",
            "default": 3600
          },
          "slackWebhookUrl": {
            "title": "Slack webhook URL (API only)",
            "type": "string",
            "description": "API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Slack incoming-webhook URL for the digest."
          },
          "discordWebhookUrl": {
            "title": "Discord webhook URL (API only)",
            "type": "string",
            "description": "API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Discord channel webhook URL for the digest."
          },
          "teamsWebhookUrl": {
            "title": "Microsoft Teams webhook URL (API only)",
            "type": "string",
            "description": "API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Teams Workflows webhook URL for the digest."
          },
          "googleChatWebhookUrl": {
            "title": "Google Chat webhook URL (API only)",
            "type": "string",
            "description": "API only — hidden on the form, where <code>sendAlertsTo</code> takes it. Google Chat space webhook URL for the digest."
          },
          "telegramUrl": {
            "title": "Telegram sendMessage link (API only)",
            "type": "string",
            "description": "API only — hidden on the form, where <code>sendAlertsTo</code> takes it. The bot's sendMessage link with its chat_id."
          },
          "autoSwitchToLive": {
            "title": "Typed search next to the sample starts the live search (API only)",
            "type": "boolean",
            "description": "API only — hidden on the form. On (default): search terms typed next to the untouched sample records start the live search (spend-capped; <code>OUTPUT.switchedToLive</code> names them). Off: the mode you send is the mode that runs — the sample stays the free demo and the status names the terms it did not search. API pipelines that must never start a paid read on their own: send <code>false</code>.",
            "default": true
          },
          "emailField": {
            "title": "E-mail column (API only)",
            "type": "string",
            "description": "API only — hidden on the form. Only for <b>dataset</b> / <b>list</b> mode: the field that holds the address in your rows (default <code>email</code>). Rows that already carry BounceVerify's result are used as they are; rows with only an address are verified first.",
            "default": "email"
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}