{
  "openapi": "3.0.1",
  "info": {
    "title": "Dutch KvK & BTW Website Scraper — Company Data & Contacts",
    "description": "Turn Dutch company domains into verified B2B/KYB leads from each site's statutory art. 3:15d disclosure: legal name, KvK-nummer, btw-identificatienummer validated with both Dutch check digits, address, e-mail and phone — plus the page each field came from. $0.009 per delivered lead.",
    "version": "0.1",
    "x-build-id": "2crSyc9zjfMdBwa0s"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/scrapersdelight~nl-kvk-website-contact-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-scrapersdelight-nl-kvk-website-contact-scraper",
        "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/scrapersdelight~nl-kvk-website-contact-scraper/runs": {
      "post": {
        "operationId": "runs-sync-scrapersdelight-nl-kvk-website-contact-scraper",
        "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/scrapersdelight~nl-kvk-website-contact-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-scrapersdelight-nl-kvk-website-contact-scraper",
        "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": {
          "domains": {
            "title": "Company domains",
            "type": "array",
            "description": "One entry per Dutch company. Paste bare domains (\"brandforlife.nl\"), full homepage URLs (\"https://www.b2-advocaten.nl\"), a direct /contact or /algemene-voorwaarden URL, or even an e-mail address — everything is normalised to the registrable domain (eTLD+1), so one company = one row = at most one charge. Leave empty to run the documented three-domain sample.",
            "items": {
              "type": "string"
            }
          },
          "startUrls": {
            "title": "Start URLs",
            "type": "array",
            "description": "The same input in Apify's Start-URLs shape, so the Console link-list UX and \"link list from a file\" uploads work. Merged with Company domains.",
            "default": [],
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL of a web page",
                  "format": "uri"
                }
              }
            }
          },
          "sourceDatasetId": {
            "title": "Source dataset ID",
            "type": "string",
            "description": "Chain straight off another Actor's dataset — a Google Maps Netherlands run, a Telefoonboek.nl scrape, a Funda makelaar list — instead of pasting a list. Every item's website field becomes an input domain.",
            "default": ""
          },
          "sourceDatasetField": {
            "title": "Source dataset field",
            "type": "string",
            "description": "Which field of the source dataset holds the website. Falls back automatically through website, url, domain, web, site, homepage, link, companyWebsite.",
            "default": "website"
          },
          "domainsFileUrl": {
            "title": "Domain list URL (.txt / .csv)",
            "type": "string",
            "description": "Public URL of a newline-delimited .txt or a .csv/.tsv of domains, for very large lists. A CSV header naming the source field (or website/url/domain) picks the column; otherwise the most domain-looking column is used. A UTF-8 BOM is stripped, so an Excel export works.",
            "default": ""
          },
          "excludeDomains": {
            "title": "Exclude domains (suppression list)",
            "type": "array",
            "description": "Already-contacted or unwanted domains. Applied BEFORE any request is made, so an excluded domain never costs a fetch and can never be billed.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "maxItems": {
            "title": "Max domains",
            "minimum": 0,
            "type": "integer",
            "description": "Hard cap on domains processed this run — your billing guard. Default 1000. Set 0 to process the whole input list.",
            "default": 1000
          },
          "requestConcurrency": {
            "title": "Request concurrency",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "Domains processed in parallel. 8 is the measured stable setting on this corpus; the target sites are small business hosts, so keep it modest.",
            "default": 8
          },
          "maxRequestsPerDomain": {
            "title": "Max pages per domain",
            "minimum": 1,
            "maximum": 30,
            "type": "integer",
            "description": "Caps the ladder: homepage + discovered statutory links + URL guesses. The Dutch disclosure is scattered across /contact, /algemene-voorwaarden, /privacy and /over-ons, so this is the single biggest lever on both fill rate and cost. Measured average on a real MKB list is well under the default.",
            "default": 8
          },
          "requestTimeoutSecs": {
            "title": "Request timeout (seconds)",
            "minimum": 5,
            "maximum": 120,
            "type": "integer",
            "description": "Per-request timeout. Each attempt also carries an external hard cap, because a stalled proxied request can outlive this timer.",
            "default": 20
          },
          "maxRetriesPerRequest": {
            "title": "Max retries per request",
            "minimum": 1,
            "maximum": 8,
            "type": "integer",
            "description": "Attempts per URL, each on a FRESH proxy IP. Retrying a flagged IP through the same IP is only a slower way to get the same answer, which is why this loop exists instead of the HTTP client's own retry.",
            "default": 3
          },
          "proxyConfiguration": {
            "title": "Proxy",
            "type": "object",
            "description": "Apify DATACENTER proxy is the default and was MEASURED sufficient across a 716-domain Dutch MKB corpus. Switch to RESIDENTIAL only for the minority of sites behind an enterprise bot wall.",
            "default": {
              "useApifyProxy": true
            }
          },
          "proxyCountry": {
            "title": "Proxy country",
            "enum": [
              "none",
              "NL",
              "BE",
              "DE",
              "FR",
              "GB",
              "US"
            ],
            "type": "string",
            "description": "Country pin. Applies ONLY when the proxy above uses the RESIDENTIAL group — datacenter pools ignore a country pin.",
            "default": "none"
          },
          "escalateToResidentialOnBlock": {
            "title": "Escalate to residential on a block",
            "type": "boolean",
            "description": "Retry a 403/429/challenge page on RESIDENTIAL-NL. Off by default because it costs residential traffic on every blocked page; turn it on when a run reports a high blocked count.",
            "default": false
          },
          "userAgent": {
            "title": "User-Agent override",
            "type": "string",
            "description": "Advanced: replace the default desktop Chrome User-Agent.",
            "default": ""
          },
          "customHeaders": {
            "title": "Custom headers",
            "type": "object",
            "description": "Advanced: extra request headers merged over the defaults (Accept-Language is nl-NL,nl;q=0.9,en;q=0.7).",
            "default": {}
          },
          "respectRobotsTxt": {
            "title": "Respect robots.txt",
            "type": "boolean",
            "description": "Read each site's robots.txt and skip URLs its wildcard user-agent group disallows. Off by default; the art. 3:15d block is a statutory public disclosure and the pages carrying it are normally crawlable.",
            "default": false
          },
          "disclosurePaths": {
            "title": "URL-guess ladder",
            "type": "array",
            "description": "Tried when the homepage exposes no statutory link. Ordered by measured yield on a real Dutch MKB corpus — /contact first, then /algemene-voorwaarden, then /over-ons and /privacy. Extend or replace freely.",
            "default": [
              "/contact",
              "/contact/",
              "/algemene-voorwaarden",
              "/over-ons",
              "/privacy",
              "/disclaimer",
              "/contactgegevens",
              "/algemene-voorwaarden/",
              "/privacyverklaring",
              "/voorwaarden"
            ],
            "items": {
              "type": "string"
            }
          },
          "extendedPaths": {
            "title": "Extended path ladder (opt-in)",
            "type": "array",
            "description": "A second, slower tier tried only when the first ladder finds nothing. Empty by default because it costs extra requests on every unresolved domain. Suggested: /klantenservice, /colofon, /privacybeleid, /bedrijfsgegevens, /wie-zijn-wij.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "scoreAllCandidates": {
            "title": "Read every statutory page, not just the first",
            "type": "boolean",
            "description": "Evaluate EVERY discovered statutory page and keep the best, instead of stopping at the first one that loads. Keep this ON: first-hit-wins is how a franchise's /contact page hands you the franchisor's KvK number.",
            "default": true
          },
          "mergeAcrossPages": {
            "title": "Merge fields across pages",
            "type": "boolean",
            "description": "Dutch law names no disclosure page, so a site scatters the block: /contact carries the phone, /algemene-voorwaarden carries the KvK number. With this ON the row is assembled from all of them and fieldSources records which page each field came from. The legal name, KvK and BTW are still taken from ONE page, so two companies can never be stitched into one row.",
            "default": true
          },
          "useStructuredData": {
            "title": "Read structured data too",
            "type": "boolean",
            "description": "Also read schema.org JSON-LD (vatID / taxID / legalName / PostalAddress) and the inline JSON payloads Dutch WordPress themes render (an ACF block holding kvk_number and btw_number). Fills ONLY what the visible text did not, so a stale JSON-LD block can never override a live footer.",
            "default": true
          },
          "validateBtwNumber": {
            "title": "Validate the btw-identificatienummer",
            "type": "boolean",
            "description": "Run both Dutch check-digit algorithms locally, with zero external calls, and emit btwValid + btwCheckMethod: the classic elfproef for rechtspersonen, and the mod-97 check for the btw-id issued to eenmanszaken since 2020, which is random and passes no elfproef. A validator that knows only the first rejects every sole trader registered in the last six years.",
            "default": true
          },
          "verifyBtwWithVies": {
            "title": "Check the BTW against EU VIES (opt-in)",
            "type": "boolean",
            "description": "A second, different question: btwValid says the number is well formed and its check digit is right; VIES says whether the registration is still open. They disagree in real life — a number can pass the elfproef while VIES answers INVALID. Off by default: it is an external service with its own rate limits. A member state that does not answer returns null, NEVER false, so you can never get a false invalid.",
            "default": false
          },
          "verifyMx": {
            "title": "Verify the e-mail domain accepts mail (MX)",
            "type": "boolean",
            "description": "DNS MX lookup on the extracted e-mail's domain; emits emailMxValid. One DNS round-trip per unique mail domain.",
            "default": false
          },
          "includePersonalNames": {
            "title": "Include personal names (bestuurder / directeur)",
            "type": "boolean",
            "description": "Extract the named director or owner when the site publishes one. Personal data under the AVG/GDPR — OFF by default, and enabling it makes you the controller for that field. Note that Dutch law does not require a director to be named, so this fills at a far lower rate than the German Geschäftsführer field it mirrors.",
            "default": false
          },
          "includePersonalEmails": {
            "title": "Include personal e-mail addresses",
            "type": "boolean",
            "description": "Include voornaam.achternaam@ style addresses. OFF by default — only role addresses (info@, contact@, administratie@, privacy@ …) are returned.",
            "default": false
          },
          "minFieldsRequired": {
            "title": "Minimum fields for a billable lead",
            "minimum": 0,
            "maximum": 4,
            "type": "integer",
            "description": "How many of {KvK number, BTW number, e-mail, phone} a company must yield before the row counts as a resolved lead (status: ok) and is therefore charged. Anything below the bar is delivered as status: partial, free.",
            "default": 2
          },
          "requireKvk": {
            "title": "Only rows with a KvK number",
            "type": "boolean",
            "description": "Drop every row whose KvK-nummer could not be read. Dropped rows are never pushed and never billed.",
            "default": false
          },
          "requireBtw": {
            "title": "Only rows with a BTW number",
            "type": "boolean",
            "description": "Drop every row whose btw-identificatienummer could not be read. Useful when you need a VAT number for invoicing or reverse-charge checks.",
            "default": false
          },
          "onlyValidBtw": {
            "title": "Only rows whose BTW passes the check digit",
            "type": "boolean",
            "description": "Keep only rows where btwValid is true. A published number that fails both Dutch check algorithms is usually a typo on the site — this drops it rather than shipping it as a fact.",
            "default": false
          },
          "legalFormFilter": {
            "title": "Filter by rechtsvorm",
            "type": "array",
            "description": "Keep only companies whose legal form matches, e.g. [\"BV\"] for besloten vennootschappen or [\"Stichting\"] for foundations. Accepted: BV, NV, VOF, CV, Stichting, Vereniging, Maatschap, Eenmanszaak. Empty = keep everything.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "postalCodeFilter": {
            "title": "Filter by postcode prefix",
            "type": "array",
            "description": "Postcode prefixes, e.g. [\"10\", \"30\"] for the Amsterdam and Rotterdam ranges, or [\"1012\"] for one district. Empty = keep everything.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "cityFilter": {
            "title": "Filter by city",
            "type": "array",
            "description": "Exact city names, e.g. [\"Amsterdam\", \"Utrecht\"]. Case-insensitive. Empty = keep everything.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "onlyResolved": {
            "title": "Only push resolved leads",
            "type": "boolean",
            "description": "Drop partial, not-found, blocked and unreachable rows entirely. OFF by default so you can see exactly what happened to every domain you submitted — those rows are always free.",
            "default": false
          },
          "dedupeByDomain": {
            "title": "Deduplicate by domain",
            "type": "boolean",
            "description": "Collapse subdomains onto their registrable domain (eTLD+1) before fetching. Exact duplicate hostnames are ALWAYS collapsed regardless of this switch — that is the double-charge guard, not a preference.",
            "default": true
          },
          "dedupeByKvk": {
            "title": "Deduplicate by KvK number",
            "type": "boolean",
            "description": "Collapse several brand domains that turn out to share one KvK-nummer into a single row. Off by default, because you usually want a row per domain you submitted.",
            "default": false
          },
          "includeRawText": {
            "title": "Include the raw disclosure text",
            "type": "boolean",
            "description": "Attach the winning page's plain text (rawText) for audit or LLM post-processing. Off by default — it multiplies dataset size.",
            "default": false
          },
          "outputMode": {
            "title": "Output mode",
            "enum": [
              "lead",
              "full"
            ],
            "type": "string",
            "description": "\"Lead\" is one flat row per company. \"Full\" adds emails[], phones[], every KvK and BTW number found, the URLs tried, the per-page scores and which fields came from structured data.",
            "default": "lead"
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}