{
  "openapi": "3.0.1",
  "info": {
    "title": "Portuguese NIPC Scraper - Company Data & Contacts",
    "description": "Turn Portuguese company domains into B2B leads from each site's statutory art. 171 CSC block: firma, legal form, sede, conservatória, mod-11-checked NIPC, capital social, VAT, email and phone. Charged only for rows with a statutory identifier. $1.70 per 1,000 companies.",
    "version": "0.1",
    "x-build-id": "ZORsmxAoWkbmDDqt6"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/scrapersdelight~pt-csc171-website-contact-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-scrapersdelight-pt-csc171-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~pt-csc171-website-contact-scraper/runs": {
      "post": {
        "operationId": "runs-sync-scrapersdelight-pt-csc171-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~pt-csc171-website-contact-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-scrapersdelight-pt-csc171-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": "Portuguese company domains (or legal-page URLs)",
            "type": "array",
            "description": "One entry per company. Accepts a bare domain (\"pingodoce.pt\"), a homepage URL (\"https://www.radiopopular.pt\") or a direct legal-page URL (\"https://x.pt/termos-e-condicoes\"). ANY TLD is accepted — Portuguese companies trade on .com and .com.pt as well as .pt. The Actor finds each site's statutory art. 171 CSC block (firma, tipo, sede, conservatória, NIPC, capital social) and parses it into one lead per domain. Leave empty to run the built-in Portuguese demo batch.",
            "items": {
              "type": "string"
            }
          },
          "startUrls": {
            "title": "Start URLs (list source)",
            "type": "array",
            "description": "The same domain list handed over as URLs, so Make, Zapier, Clay or a Google Sheet can pass it natively (a link to a text/CSV file of URLs also works). Merged with \"Portuguese company domains\".",
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL of a web page",
                  "format": "uri"
                }
              }
            }
          },
          "sourceDatasetId": {
            "title": "Enrich an existing dataset",
            "type": "string",
            "description": "Dataset ID of a previous Actor run. Each item's domain / website column is read and enriched — the real agency workflow: run a Google Maps, Páginas Amarelas or directory scraper first, then pipe its output here."
          },
          "domainFieldName": {
            "title": "Domain field / CSV column",
            "type": "string",
            "description": "Which field of the source dataset (or which CSV column of the list file) holds the domain. Leave empty to auto-detect domain / website / site / sitio / url. Dotted paths like \"company.website\" work."
          },
          "domainsFileUrl": {
            "title": "Domain list file URL",
            "type": "string",
            "description": "URL of a CSV, TXT, JSON or JSONL file holding the domains — for lists too big to paste into the editor. The column is picked with \"Domain field / CSV column\"."
          },
          "skipDomains": {
            "title": "Suppression list",
            "type": "array",
            "description": "Domains to skip outright — accounts you already own, competitors, do-not-contact entries. Matched on the registrable domain, so \"www.x.pt/pagina\" and \"x.pt\" are the same entry.",
            "items": {
              "type": "string"
            }
          },
          "previousDatasetId": {
            "title": "Skip rows already delivered (dataset ID)",
            "type": "string",
            "description": "Dataset ID of an earlier run of THIS Actor. Its stableId / NIPC / domain values are loaded as a suppression list, so a monthly re-run never re-delivers — and never re-charges you for — a company you already have."
          },
          "maxItems": {
            "title": "Max results (billed rows)",
            "minimum": 0,
            "type": "integer",
            "description": "Hard cap on rows DELIVERED AND BILLED this run. Distinct from \"Max domains\": art. 171 CSC binds sociedades, not sole traders, and many sites never publish the block, so a mixed Portuguese list yields a fraction of its domains as billed rows. 0 = no cap.",
            "default": 1000
          },
          "maxDomains": {
            "title": "Max domains attempted",
            "minimum": 0,
            "type": "integer",
            "description": "Cap on domains ATTEMPTED, applied before any request. Use it to sample a big list cheaply. 0 = attempt every domain supplied.",
            "default": 0
          },
          "maxDiscoveryRequestsPerDomain": {
            "title": "Max discovery requests per domain",
            "minimum": 0,
            "maximum": 40,
            "type": "integer",
            "description": "Hard cap on discovery + confirmation requests per domain, counted AFTER the homepage. This is the knob that stops one slow host burning a minute of a run.",
            "default": 8
          },
          "maxPagesParsed": {
            "title": "Max pages parsed per domain",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "How many SUBPAGES may be parsed and merged for one domain, on top of the homepage. Raising it finds more fields on sites that split the block across Termos e Condições, Política de Privacidade and Contactos; lowering it makes each domain cheaper.",
            "default": 3
          },
          "perDomainTimeoutSecs": {
            "title": "Per-domain timeout (seconds)",
            "minimum": 5,
            "maximum": 600,
            "type": "integer",
            "description": "Wall-clock deadline for one domain, all discovery included.",
            "default": 60
          },
          "requestConcurrency": {
            "title": "Request concurrency",
            "minimum": 1,
            "maximum": 30,
            "type": "integer",
            "description": "How many domains are worked in parallel. Higher is faster; keep it modest to stay polite to small business sites. The Actor lowers it automatically, with a warning, when the memory allocated to the run cannot support it — roughly 70 MB per parallel domain.",
            "default": 10
          },
          "requestTimeoutSecs": {
            "title": "Request timeout (seconds)",
            "minimum": 5,
            "maximum": 120,
            "type": "integer",
            "description": "Timeout for a single HTTP request.",
            "default": 25
          },
          "maxRequestRetries": {
            "title": "Max request retries",
            "minimum": 0,
            "maximum": 6,
            "type": "integer",
            "description": "Retries after the first attempt. The first attempt is DIRECT; a transient error gets one more direct try, and a block (403 / challenge) is retried on RESIDENTIAL exit nodes with a FRESH session each time.",
            "default": 2
          },
          "discoveryChannels": {
            "title": "Discovery channels",
            "type": "array",
            "description": "Which channels may be used to locate the statutory block. homepage = the homepage itself (footer + JSON-LD); anchor = the site's own same-site links ranked by Portuguese labels (Ficha Técnica, Informação Legal, Aviso Legal, Termos e Condições, Quem Somos, Política de Privacidade, Contactos); sitemap and wpJson run ONLY when the footer offered no candidate link; pathGuess (off by default) tries ten conventional Portuguese paths.",
            "items": {
              "type": "string",
              "enum": [
                "homepage",
                "anchor",
                "sitemap",
                "wpJson",
                "pathGuess"
              ],
              "enumTitles": [
                "Homepage footer + JSON-LD (free)",
                "Ranked Swedish links (kopvillkor / om oss / integritetspolicy)",
                "XML sitemap",
                "WordPress REST page index",
                "Guessed Swedish paths (measured: 0 wins in 240 domains)"
              ]
            },
            "default": [
              "homepage",
              "anchor",
              "sitemap",
              "wpJson"
            ]
          },
          "followWwwAndRootVariants": {
            "title": "Try www and apex variants",
            "type": "boolean",
            "description": "If the homepage fails, retry the other host form (www.x.pt to x.pt and back) before declaring the domain unreachable.",
            "default": true
          },
          "deepJsDiscovery": {
            "title": "Deep JS fallback (scan bundles for a legal-page URL)",
            "type": "boolean",
            "description": "A cheap, browser-free channel for a footer that only exists after client-side render: the Actor reads the page's inline JSON payloads and its external JavaScript bundles, searching them for a legal-page URL. Costs up to 6 extra requests per domain and is never charged separately.",
            "default": false
          },
          "respectRobotsTxt": {
            "title": "Respect robots.txt",
            "type": "boolean",
            "description": "Skip pages the site's robots.txt disallows. Costs one extra request per domain. Off by default.",
            "default": false
          },
          "proxyConfiguration": {
            "title": "Proxy",
            "type": "object",
            "description": "Leave as is for the measured default: DIRECT requests first, then Apify RESIDENTIAL (Portugal) only for a host that actually refuses us. Supply your own proxy URLs or an Apify proxy group here to route the FIRST attempt through them instead.",
            "default": {
              "useApifyProxy": false
            }
          },
          "proxyCountry": {
            "title": "Residential exit country",
            "type": "string",
            "description": "ISO country code for the RESIDENTIAL retry. PT by default, because some Portuguese retailers geo-tailor or geo-block. Only used when a host refuses the direct request.",
            "default": "PT"
          },
          "escalateToResidentialOnBlock": {
            "title": "Escalate to residential on a block",
            "type": "boolean",
            "description": "On a 403 / challenge / connection reset (never on a dead host and never on a 404), retry on RESIDENTIAL exit nodes pinned to the country above, a fresh session per attempt. It only fires on an actual refusal, so it costs nothing on a clean list.",
            "default": true
          },
          "escalateToUnblockerOnBlock": {
            "title": "Escalate to Apify Unblocker on a block",
            "type": "boolean",
            "description": "A SECOND escalation, after residential, for the Cloudflare-managed-challenge tail. Off by default because Unblocker requests are billed to your Apify account on top of the row price.",
            "default": false
          },
          "customUserAgent": {
            "title": "Custom User-Agent",
            "type": "string",
            "description": "Override the browser User-Agent sent on every request. Leave empty for the built-in Chrome 124 fingerprint."
          },
          "extraHttpHeaders": {
            "title": "Extra HTTP headers",
            "type": "object",
            "description": "Additional request headers, merged over the defaults (e.g. a From: header identifying your crawler)."
          },
          "tldFilterMode": {
            "title": "TLD filter mode",
            "enum": [
              "none",
              "include",
              "exclude"
            ],
            "type": "string",
            "description": "No TLD filter is applied by default, deliberately: a real Portuguese business corpus is only part .pt — the rest is .com, .com.pt, .eu and others — so filtering to .pt would discard real companies. Use \"include\" or \"exclude\" with the list below.",
            "default": "none"
          },
          "tldFilter": {
            "title": "TLDs",
            "type": "array",
            "description": "The TLD list the mode above applies to, without the dot: pt, com, com.pt, eu.",
            "items": {
              "type": "string"
            }
          },
          "requireNipc": {
            "title": "Only rows with an NIPC",
            "type": "boolean",
            "description": "Deliver (and bill) only companies whose block carries a mod-11-valid collective NIPC. Rows identified only by a PT VAT number, or by firma plus conservatória / capital social, are filtered out here and never billed.",
            "default": false
          },
          "requireCapitalSocial": {
            "title": "Only rows that state the capital social",
            "type": "boolean",
            "description": "Deliver (and bill) only companies that print their capital social, which art. 171 n.º 2 requires of every Lda., S.A. and sociedade em comandita por acções.",
            "default": false
          },
          "requireContact": {
            "title": "Only rows with a contact",
            "type": "boolean",
            "description": "Deliver (and bill) only companies with an email or a phone number.",
            "default": false
          },
          "excludeLiquidation": {
            "title": "Exclude companies em liquidação or insolvência",
            "type": "boolean",
            "description": "Art. 171 n.º 1 obliges a company in liquidation to say so (\"sendo caso disso, a menção de que a sociedade se encontra em liquidação\"). Turn this on to drop those rows (and insolvência / PER statements with them) so they are never billed.",
            "default": false
          },
          "legalFormFilter": {
            "title": "Legal forms",
            "type": "array",
            "description": "Keep only rows whose legal form contains one of these words, e.g. \"Lda.\" , \"Unipessoal\", \"S.A.\", \"SGPS\", \"Cooperativa\". Empty = every form.",
            "items": {
              "type": "string"
            }
          },
          "minFieldsRequired": {
            "title": "Minimum populated fields",
            "minimum": 0,
            "maximum": 18,
            "type": "integer",
            "description": "Quality floor: a row must carry at least this many of the 18 value fields before it is delivered and billed. 0 = no floor.",
            "default": 0
          },
          "emailPolicy": {
            "title": "Email policy",
            "enum": [
              "all",
              "role-only",
              "exclude-role"
            ],
            "type": "string",
            "description": "Agencies split hard on whether info@ / kontakt@ counts as a lead. \"Role only\" keeps just those; \"Exclude role\" keeps only named mailboxes.",
            "default": "all"
          },
          "dedupeBy": {
            "title": "Deduplicate by",
            "enum": [
              "nipc-then-domain",
              "nipc",
              "domain",
              "none"
            ],
            "type": "string",
            "description": "Which key collapses duplicates BEFORE anything is pushed or charged. The default uses the NIPC when the block carries a mod-11-valid one and the registrable domain otherwise, so two domains owned by the same company collapse into one billed row.",
            "default": "nipc-then-domain"
          },
          "validateTaxId": {
            "title": "Checksum-validate the identifiers",
            "type": "boolean",
            "description": "Run the Portuguese mod-11 check digit on every NIPC and PT VAT number found and emit the result. A number that fails is never promoted to the NIPC column, and no number is ever repaired or completed.",
            "default": true
          },
          "includeSoleTraderNif": {
            "title": "Include a sole trader's personal NIF when one is published",
            "type": "boolean",
            "description": "Off by default. A sole trader (empresário em nome individual) prints an individual NIF (prefix 1, 2, 3, 45 or the retired 8 range) instead of an NIPC. These domains are reported as status sole_trader and never billed; turn this on to also return the number in soleTraderNif.",
            "default": false
          },
          "extractOfficer": {
            "title": "Extract a named officer (gerente / administrador)",
            "type": "boolean",
            "description": "Return a gerente, administrador or encarregado de proteção de dados named next to a label on the pages read (officerName, officerRole). Off by default.",
            "default": false
          },
          "extractSocials": {
            "title": "Extract social profiles",
            "type": "boolean",
            "description": "LinkedIn, Facebook, Instagram, X, YouTube and TikTok links present on the pages read.",
            "default": true
          },
          "extractPolicyUrls": {
            "title": "Extract terms & privacy-policy URLs",
            "type": "boolean",
            "description": "The site's terms and privacy-policy URLs, linked from the pages read.",
            "default": true
          },
          "includeMissRows": {
            "title": "Include unbilled miss rows",
            "type": "boolean",
            "description": "Emit a row for every domain that produced no lead — dead host, blocked, publishes no statutory block, contact details only, a sole trader, filtered out, a duplicate, or suppressed — with its status and missReason, so you can do coverage accounting. These rows are never billed.",
            "default": false
          },
          "includeFieldSources": {
            "title": "Include per-field provenance",
            "type": "boolean",
            "description": "Add a fieldSources object naming the exact URL each field was read from. Useful when the block is split across a Termos e Condições page and a Contactos page and you need to audit which said what.",
            "default": false
          },
          "flattenOutput": {
            "title": "Flat columns",
            "type": "boolean",
            "description": "On: one flat row, ready for Google Sheets, Clay or a CSV export. Off: fields grouped into company {}, registry {}, contact {}, people {} and policies {} objects.",
            "default": true
          },
          "verifyWithVies": {
            "title": "Cross-check each NIPC with EU VIES (live VAT status + registered name)",
            "type": "boolean",
            "description": "For every billed row with an NIPC, ask the European Commission VIES service whether PT+NIPC is an ACTIVE VAT registration today and under what registered name, and add viesStatus / viesName / viesAddress. A checksum proves a number is well formed; VIES says it is live. Measured on 156 held-out NIPCs: 148 VALID, 8 INVALID, and the VIES name agreed with the firma read off the site on 111 of 114 comparable rows. Adds roughly 0.5-1 s per row (the EU endpoint is rate-limited, so calls are serialised). Never changes what is billed.",
            "default": false
          },
          "saveRawPages": {
            "title": "Save the raw pages read (audit trail)",
            "type": "boolean",
            "description": "Store every page the parser actually read, gzipped, in this run's key-value store (key <domain>__<n>), so any row can be re-checked against the exact bytes it came from. Adds storage cost; off by default.",
            "default": false
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}