{"_id":"crawlee-ghost-fetch","_rev":"9-1df830d044b02782699e375a5b1e95dd","name":"crawlee-ghost-fetch","dist-tags":{"latest":"0.10.0"},"versions":{"0.4.0":{"name":"crawlee-ghost-fetch","version":"0.4.0","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.4.0","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"8c7355dda83bee25d46b01d2127865c0fab76227","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.4.0.tgz","fileCount":31,"integrity":"sha512-MCLAVymVbxlKQn9MTbmWnuTL6VT5FWm5pf2Ym4sh6vucYYt/qJmKdWOnVpYwANDNEZoIdIvfklUu4oXGy/3EiQ==","signatures":[{"sig":"MEYCIQD/mzVoSDawENwhLdfiDbLCZtJKCo84DlmT69kDMH2fXwIhAKVy6q6wstWW5bd5P7osCowAYcA+m6xsT7/9tqibVxiS","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":174283},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"d4d6d1a3cebb68b05220a0c1fb5ae1168cee0a34","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.4.0_1778254793590_0.4096321602161559","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"crawlee-ghost-fetch","version":"0.5.0","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.5.0","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"0fd6781f9c11577023cb064271ce3e9f9990c6a0","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.5.0.tgz","fileCount":31,"integrity":"sha512-gmAqaNKpvBIwieK4Kk6g9Qlfr/6FAc+vnMpl1BFc7zlhP8Gbwf5f3asDqZFarcm4sIaF/5dwwydW5fp7b8AqTg==","signatures":[{"sig":"MEUCIBM5bwk521J7epk6AmEtULkV9NYYVoQBuEIEdk0DXkgeAiEA16up3F1ZlxN4EK3nxNwOOXg8RQ5YHauLNuDcB9+hCOI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":181547},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b4960d151588d8eccf24af0935970e465bd90c79","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.5.0_1778336623763_0.2092427239739092","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"crawlee-ghost-fetch","version":"0.6.0","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.6.0","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"7ed1b8f2a5a968398c99e655ce9fcef5d070c263","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.6.0.tgz","fileCount":31,"integrity":"sha512-f4IO5/0yrDqd7isBWWzm++6nGRrit2yu1CDIm1pi++WvcAP4g6pIvR1Lu1tfrdP+Gzz5uh1QbuTXR2Zj1lDhDA==","signatures":[{"sig":"MEUCIQDuNRpPfTlCUL0Yp/DyyZMxr3aKNNYnoDQ6WWvyCqNIoAIgH9w+YAY1Un8DF+Ecvzi+YiZEq4xM8/WVL7QKI8tU9vc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":181382},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"4b58ef325088ea266b57eb8803c680f878368e0f","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.6.0_1778929520758_0.18376797172451598","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"crawlee-ghost-fetch","version":"0.7.0","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.7.0","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"ec0c048f75577cf57fc58fc1ee03977eb2d6e9c9","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.7.0.tgz","fileCount":31,"integrity":"sha512-ZTVHQgsaKGt0YlKNSpGcN/rAmCuCai0QjljSBrXmbzLB/9Trl5qOVEkL60WZAl6R89pst4Fx73cDJ9ymIXX1Lw==","signatures":[{"sig":"MEUCIQDJsagVaik7jQB/y7bwdTDJ6naSsj0fKfC7DfiiuW2JOgIgUmyNDm3FqHljxTnUYDVZ7q2e8o5mDz2MgNvNKAgZeZ4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":196896},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b47c02688be2b9554a5b84c8b080930cd2e3460f","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.7.0_1778953475168_0.05480071507199624","host":"s3://npm-registry-packages-npm-production"}},"0.7.1":{"name":"crawlee-ghost-fetch","version":"0.7.1","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.7.1","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"40111d3b6e6a31a17cc9fff8465453b57a204756","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.7.1.tgz","fileCount":31,"integrity":"sha512-IfnIgHFH0HWCbjr30NwrwSlW3guNGcO/F30MWk5DmrGWgEA6MZp8Q6TikJrmYz0BuP5s0t8apkSLaKD2CGXhhg==","signatures":[{"sig":"MEQCIAL2RxgIn4lw/c3jY4EjQB3ErOOLTYPNwmq65ovnGB/rAiANhs8R4AKYutwD/hc3h4H5zISrKX3qj+7P9v4/hOn+6g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":198559},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b47c02688be2b9554a5b84c8b080930cd2e3460f","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.7.1_1779104325592_0.7648568252247732","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"crawlee-ghost-fetch","version":"0.8.0","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.8.0","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"b8e8cc0fecedb8f86ebc9feda856303bbe88be19","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.8.0.tgz","fileCount":31,"integrity":"sha512-Gqpv7J+21qpGk7nPj1VvQnyMmmsTeATIlxZRO8LDSmBA/ZkkMaBbPb1jQX5jKmFxNuPwEOeUbvrAYBJPKiix9Q==","signatures":[{"sig":"MEYCIQCR5vlnfMSEu5jYsXdMYVDziV+BfTM/nstGKqeswJpc0gIhAMGJmqnlHZ7bIoZRjtVcEgRV5IYqjKJKQz04FFQvWVcr","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":209916},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b47c02688be2b9554a5b84c8b080930cd2e3460f","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.8.0_1780337036534_0.5024262354875699","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"crawlee-ghost-fetch","version":"0.9.0","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.9.0","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"b5cbbd652af9f15d9f199ae747d69eee7c0aca4e","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.9.0.tgz","fileCount":31,"integrity":"sha512-3oLsqkMBOnBi5DqLYqMZQmq/j9d3GSSNxUc9EwkgjtLx79r/Jxd3bqQf3LoU7s5zUY6NdsI2/RyZAbkCT70Z9w==","signatures":[{"sig":"MEQCIE06ruyoLMVcqYQmIuFf71ry+7UQKkHRpg4OkJJOLShMAiBtBN5D45+gv5ZPg+jKhEWsdXTTpdUvQ7jBxJYwenwsYA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":208184},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"f902d68c143580983950263d22e9851de8243c9c","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.9.0_1780572116200_0.3551039625227068","host":"s3://npm-registry-packages-npm-production"}},"0.9.1":{"name":"crawlee-ghost-fetch","version":"0.9.1","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","_id":"crawlee-ghost-fetch@0.9.1","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"dist":{"shasum":"aa7cdff9892da729228c146e85d92a96c36d60f7","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.9.1.tgz","fileCount":31,"integrity":"sha512-nr8uMKsnNzQqgVtA3dRqMnyY7DuuZM1ehD97jqsl8EBW2RPpsn0r8rsBtoNPDJLVeRa6ELI8BiDVuoAC/o4lrw==","signatures":[{"sig":"MEYCIQCeRNS5VhEhMFe2n8tVb6e2JEblX+48oISExD/18g77/AIhAPTHFPMGcHBAqcQYFSHrxJ51m4/mWW4T/f/ZP9XS6Nif","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":209867},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"ce0bb401312f9981d8307329e15d5f97a87bee34","scripts":{"lint":"eslint src tests","test":"tsx --test tests/*.test.ts","build":"tsc","prepare":"npm run build","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"repository":{"url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git","type":"git"},"_npmVersion":"11.12.1","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","apify":"^3.5.2","crawlee":"^3.16.0","typescript":"^5.9.3","@types/node":"^22.15.32","@crawlee/core":"^3.16.0","@apify/tsconfig":"^0.1.1"},"peerDependencies":{"apify":"^3.4.0","crawlee":"^3.13.0","@crawlee/core":"^3.13.0"},"_npmOperationalInternal":{"tmp":"tmp/crawlee-ghost-fetch_0.9.1_1782401463844_0.7293621158557266","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"crawlee-ghost-fetch","version":"0.10.0","description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","type":"module","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"engines":{"node":">=20.0.0"},"scripts":{"build":"tsc","test":"tsx --test tests/*.test.ts","typecheck":"tsc --noEmit","lint":"eslint src tests","prepare":"npm run build","prepublishOnly":"npm run build"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git"},"peerDependencies":{"@crawlee/core":"^3.13.0","apify":"^3.4.0","crawlee":"^3.13.0"},"devDependencies":{"@apify/tsconfig":"^0.1.1","@crawlee/core":"^3.16.0","@types/node":"^22.15.32","apify":"^3.5.2","crawlee":"^3.16.0","tsx":"^4.20.3","typescript":"^5.9.3"},"keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"license":"ISC","gitHead":"cdb535cf46372b42d13ac0311fd3e6043ac16918","_id":"crawlee-ghost-fetch@0.10.0","bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-Lwf34J1C2wlURCujo9fJeuvV0Xc5busXqZLgEOZ4LOclrzrqzR2OZJ5fXTIdzMyYwokU2c/ZmtMx2t6meFd8Pg==","shasum":"2800243201bb7f9b7d44422c7c34109cdf41d9eb","tarball":"https://registry.npmjs.org/crawlee-ghost-fetch/-/crawlee-ghost-fetch-0.10.0.tgz","fileCount":31,"unpackedSize":213192,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD0+XpdONSo8Fz5HaMpZdq9TX7MVSMGcOlsb039vmxjmwIhANGm30VC4yIPTycCNbi7FPo3eHxaqHFcry8ToZwlPE3w"}]},"_npmUser":{"name":"europa6","email":"yfe.github@protonmail.com"},"directories":{},"maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/crawlee-ghost-fetch_0.10.0_1787302444020_0.5694815326973401"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-08T15:39:53.478Z","modified":"2026-08-21T08:54:04.320Z","0.4.0":"2026-05-08T15:39:53.735Z","0.5.0":"2026-05-09T14:23:43.921Z","0.6.0":"2026-05-16T11:05:21.022Z","0.7.0":"2026-05-16T17:44:35.332Z","0.7.1":"2026-05-18T11:38:45.739Z","0.8.0":"2026-06-01T18:03:56.728Z","0.9.0":"2026-06-04T11:21:56.335Z","0.9.1":"2026-06-25T15:31:03.977Z","0.10.0":"2026-08-21T08:54:04.167Z"},"bugs":{"url":"https://github.com/apify-projects/crawlee-ghost-fetch/issues"},"license":"ISC","homepage":"https://github.com/apify-projects/crawlee-ghost-fetch#readme","keywords":["crawlee","ghost-fetch","mcp","scraping","unblocker","apify"],"repository":{"type":"git","url":"git+https://github.com/apify-projects/crawlee-ghost-fetch.git"},"description":"Crawlee BaseHttpClient that delegates every HTTP request to the ghost-fetch unblocker (impit Tier 1 + stealth Chromium fallback). Plugs into CheerioCrawler / HttpCrawler with a one-line swap; SessionPool drives rotation; opt-in false-block recovery for WA","maintainers":[{"name":"europa6","email":"yfe.github@protonmail.com"}],"readme":"# crawlee-ghost-fetch\n\nA Crawlee `BaseHttpClient` that delegates every HTTP request to the\n[ghost-fetch](https://github.com/yfe404/ghost-fetch) unblocker. Drop\nit into a `CheerioCrawler` (or any `HttpCrawler`) and your crawl gets\nghost-fetch's full unblocking cascade — Tier 1 impit, Tier 2 stealth\nChromium, country-aware locale, sticky sessions — without any of the\nplumbing in your actor.\n\n## When to use\n\nYou're building (or maintaining) an Apify standby actor and:\n\n- The target site has bot protection — Akamai BMP, Cloudflare,\n  DataDome, or similar.\n- You want a single sticky session per actor process so the first call\n  pays the browser-tier warmup and every subsequent call rides the same\n  warmed cookie jar through ghost-fetch's fast Tier 1 (impit) path.\n- You're happy to keep the rest of your actor (request builders,\n  handlers, extractors, route mapping) the way you have it today —\n  this library only swaps out the HTTP fetcher.\n\nIf you're scraping an unprotected site, you don't need this — vanilla\n`CheerioCrawler` is fine.\n\n> **Upgrading to 0.9.0 (breaking).** The client now speaks ghost-fetch's\n> `POST /v1/fetch` (trace-v4) instead of `/fetch_url`. Three caller-visible\n> changes: tier/browser config moved under a single **`render: {...}`** block\n> (`hintFromUrl`→`render.mode`, `browser`/`fox`→`render.browser`/`render.firefox`);\n> a **`treatAsSuccess`** predicate now reads **`p.response.body`** (was\n> `p.content`) and overrides ghost-fetch's own verdict; and a larger\n> **`maxPoolSize`** now buys real **identity diversity** (one\n> {exit IP + cookie jar + device fingerprint} per Crawlee Session — not TLS).\n> See [CHANGELOG.md](./CHANGELOG.md) and [docs/adr/0001](./docs/adr/0001-single-sticky-session-default.md).\n\n## Install\n\n```bash\nnpm install crawlee-ghost-fetch\n```\n\n`crawlee`, `@crawlee/core`, and `apify` are **peer dependencies** —\nyour actor already has them.\n\nYou also need a ghost-fetch endpoint to talk to. Either deploy your\nown ghost-fetch standby on Apify (see the\n[ghost-fetch](https://github.com/yfe404/ghost-fetch) repo) or run it\nlocally for development. Pass the URL via `ghostFetchUrl` on the\nclient config or the `GHOST_FETCH_URL` env var. There is no default —\nthe lib throws on construction if neither is set.\n\n## Quick-start (single-country)\n\n```ts\nimport { CheerioCrawler } from 'crawlee';\nimport {\n    GhostFetchHttpClient,\n    ghostFetchCrawlerOptions,\n} from 'crawlee-ghost-fetch';\n\nconst httpClient = new GhostFetchHttpClient({\n    name: 'overstock',\n    defaultCountry: 'US',\n});\n\nconst crawler = new CheerioCrawler({\n    keepAlive: true,\n    httpClient,\n    requestHandlerTimeoutSecs: 180,\n    navigationTimeoutSecs: 120,\n    maxConcurrency: 20,\n    maxRequestRetries: 3,\n    // Identity binding/rotation is automatic — the client reads the active\n    // Crawlee session from `request.sessionToken.id`. No pre-nav hook needed.\n    ...ghostFetchCrawlerOptions(),\n    requestHandler: async ({ $, request }) => {\n        // your handler\n    },\n});\n\nawait crawler.run(['https://www.overstock.com/some/page']);\n```\n\n## Multi-country (e.g. kaufland)\n\nWhen the country is encoded in the URL — TLD, query string, subdomain —\npass a `countryFromUrl` resolver. The library invokes it per request\nand forwards the result to ghost-fetch's `country` argument:\n\n```ts\nconst KAUFLAND_TLDS = new Set(['DE', 'CZ', 'PL', 'SK', 'AT', 'IT', 'FR']);\n\nconst httpClient = new GhostFetchHttpClient({\n    name: 'kaufland',\n    defaultCountry: 'DE',\n    countryFromUrl: (url) => {\n        try {\n            const tld = new URL(url).hostname.split('.').pop()?.toUpperCase();\n            return tld && KAUFLAND_TLDS.has(tld) ? tld : undefined;\n        } catch {\n            return undefined;\n        }\n    },\n});\n```\n\nReturning `undefined` falls back to `defaultCountry`.\n\n## Per-URL warmup and render control\n\nTwo more optional resolvers, same shape as `countryFromUrl`:\n\n- **`warmupFromUrl(url) → string | undefined`** — a URL ghost-fetch navigates\n  to before fetching the target, for endpoints that need cookies from a real\n  HTML page nav first. `undefined` to skip.\n\n- **`renderFromUrl(url) → Partial<RenderOptions> | undefined`** — overrides the\n  static `render` config per URL (shallow-merged; returned keys win).\n\nThe `render` block mirrors ghost-fetch's `/v1/fetch` `render` schema:\n\n| field | values | meaning |\n|---|---|---|\n| `mode` | `auto` (default) · `http` · `browser` | cascade control. `http` = Tier 1 only (fail-fast); `browser` = skip Tier 1, go straight to the browser tier. |\n| `browser` | `chrome` (default) · `firefox` | engine family (cloakbrowser / camoufox). |\n| `screenshot` | `none` · `viewport` · `full_page` | capture a screenshot (GET only; base64 PNG on `response.screenshot`). |\n| `firefox` | `FirefoxOptions` | camoufox device-fingerprint knobs (`fox_os`, `fox_locale`, …). Firefox-only; dropped on the wire for chrome. |\n\n```ts\n// Tier-1-only on a cheap API, force the browser tier on a heavy page:\nnew GhostFetchHttpClient({\n    name: 'mysite',\n    defaultCountry: 'US',\n    renderFromUrl: (url) =>\n        url.includes('/api/') ? { mode: 'http' }\n            : url.includes('/heavy/') ? { mode: 'browser' }\n                : undefined,\n});\n```\n\n### Firefox (camoufox) device fingerprints\n\nPick the Firefox engine when a target blocks Chromium fingerprints — it ships a\ndifferent (authentic, shared) network fingerprint and a separate\ndevice-fingerprint stack:\n\n```ts\nnew GhostFetchHttpClient({\n    name: 'datadome-target',\n    defaultCountry: 'DE',\n    render: {\n        browser: 'firefox',\n        firefox: { fox_os: 'windows', fox_humanize: true, fox_block_images: true },\n    },\n});\n```\n\nPer-URL:\n\n```ts\nnew GhostFetchHttpClient({\n    name: 'multi-target',\n    defaultCountry: 'US',\n    renderFromUrl: (url) =>\n        url.includes('hard-target.com')\n            ? { browser: 'firefox', firefox: { fox_os: 'macos', fox_locale: 'en-US' } }\n            : undefined,\n});\n```\n\n`render.firefox` is dropped on the wire when the resolved browser is not\n`'firefox'` — safe to set defaults even when most URLs go through Chromium.\n\n## Identity diversity (`maxPoolSize`)\n\nEach Crawlee Session maps to one ghost-fetch Session — one coherent\n**{exit IP + cookie jar + device fingerprint}** bundle (the network/TLS\nfingerprint is authentic and shared per browser, *not* varied per session).\nThe default `maxPoolSize: 1` is one sticky identity per process (maximises\nTier-1 warm-jar reuse). For block-heavy or high-concurrency targets, raise it\nto run several distinct identities concurrently:\n\n```ts\n...ghostFetchCrawlerOptions({\n    sessionPoolOptions: { maxPoolSize: 5 },          // N concurrent identities\n    // Retire a burned identity on ghost-fetch's own signal (a fresh Session\n    // fixes a fingerprint-rejected block; rate-limit / IP-burn are self-healed\n    // server-side — don't retire on those):\n    shouldRetire: (_ctx, r) => r.diagnostics?.suspected_fingerprint_rejected === true,\n});\n```\n\nSee [docs/adr/0001](./docs/adr/0001-single-sticky-session-default.md) for the trade-off.\n\nSee `FoxOptions` in `src/types.ts` for the full param list (OS, locale,\nWebGL config, fonts, addons, blocking toggles, raw `fox_config`).\n\n## Crawler config recipe\n\n`ghostFetchCrawlerOptions(opts?)` returns the Crawlee opinions that pair\nwith the client. Identity binding is automatic — the client reads the\nactive Crawlee session from `request.sessionToken.id` (no hook required\nsince 0.9.1). Since 0.7.0, any pre/post navigation hooks you do add\ncompose **through the helper arg**, not by spread-after:\n\n```ts\nimport {\n    GhostFetchHttpClient,\n    ghostFetchCrawlerOptions,\n} from 'crawlee-ghost-fetch';\n\nconst crawler = new CheerioCrawler({\n    httpClient: new GhostFetchHttpClient({ name: 'site', defaultCountry: 'US' }),\n    ...ghostFetchCrawlerOptions({\n        // Optional retire predicate (see below). Receives status +\n        // headers only — body content is not yet parsed at this point.\n        // shouldRetire: (_, r) => r.statusCode === 403,\n    }),\n    requestHandler,\n});\n```\n\n**Do not spread caller hooks AFTER `ghostFetchCrawlerOptions()`.** That\noverwrites the helper's hooks (including the generated retire hook when\n`shouldRetire` is set). Always pass them through the helper arg.\n\nThe helper expands to:\n\n```ts\n{\n    additionalMimeTypes: ['application/octet-stream'],\n    useSessionPool: true,\n    persistCookiesPerSession: true,\n    sessionPoolOptions: { /* sticky-by-default — see below */ },\n    preNavigationHooks: [...callerHooks],\n    postNavigationHooks: [...generatedRetireHook?, ...callerHooks],\n}\n```\n\n### Sticky-by-default session pool\n\n`sessionPoolOptions` defaults to ONE session per actor process, never\nauto-retired on usage / age / 4xx:\n\n| Knob | Default | Why |\n|---|---|---|\n| `maxPoolSize` | `1` | All requests share one session → one IP, one cookie jar, one JA4. Hot-path Tier 1 amortizes fully. |\n| `sessionOptions.maxUsageCount` | `Number.MAX_SAFE_INTEGER` | Don't auto-retire on usage count. |\n| `sessionOptions.maxErrorScore` | `Number.MAX_SAFE_INTEGER` | Don't auto-retire on error score. |\n| `sessionOptions.maxAgeSecs` | `31_536_000` (1 year) | Finite (Crawlee builds a Date from this; `MAX_SAFE_INTEGER` overflows). |\n| `blockedStatusCodes` | `[]` | 4xx is often cookie-rotation noise on WAFs that bind cookies per-response, not a real block. Use `shouldRetire` for status + header decisions Crawlee's enum can't express. |\n\nOperator overrides via `opts.sessionPoolOptions`, deep-merged with these\ndefaults (`opts.sessionPoolOptions.sessionOptions` merges nested-key-wise,\nnot replaces).\n\n> **Do not enable `retryOnBlocked: true`** on the crawler. With\n> `blockedStatusCodes: []`, `HttpCrawler.isRequestBlocked()` falls back\n> to Crawlee's built-in default `[401, 403, 429]` when the pool list is\n> empty — reintroducing automatic retire outside your `shouldRetire`\n> predicate.\n\n### `shouldRetire` — retire-and-retry on status + headers\n\nPass a predicate to retire the active session and re-queue the request\nwhen ghost-fetch's response looks bad on signals Crawlee's\n`blockedStatusCodes` enum can't express:\n\n```ts\nghostFetchCrawlerOptions({\n    // Predicate sees status code + headers only; the body has not been\n    // parsed at this point. For body-shape decisions, do them inside\n    // the request handler and call `ctx.session.retire()` + throw yourself.\n    shouldRetire: (_ctx, response) => response.statusCode === 403,\n})\n```\n\nThe predicate's `response` arg is a `RetirableResponse`:\n\n```ts\ninterface RetirableResponse {\n    statusCode?: number;\n    headers?: Record<string, string | string[] | undefined>;\n}\n```\n\nBody parsing happens AFTER `postNavigationHooks` (where the generated\nretire hook lives), so any body-shape check inside `shouldRetire` would\nsee undefined / unparsed data. If you need to retire based on parsed\nbody content, do it inside `requestHandler`:\n\n```ts\nrequestHandler: async (ctx) => {\n    if (looksLikeChallenge(ctx.$('body').text())) {\n        ctx.session?.retire();\n        throw new Error('challenge body — retiring session');\n    }\n    // ...normal extraction\n}\n```\n\nReturning `true` from `shouldRetire` → the active session is marked\nbad, retired, and the request is thrown so Crawlee re-queues it with a\nfresh session (new `session.id` → new ghost-fetch session arg → fresh\nexit IP + empty cookie jar).\n\nThe generated hook runs **before** any caller-supplied\n`postNavigationHooks`, so a known-blocked response can't trigger caller\nside-effects before the retry throw.\n\n**Each `shouldRetire: true` consumes a `maxRequestRetries` slot.** For\nblock-heavy sites, bump `maxRequestRetries` from the default 3 to ~8 so\na temporary streak of retires doesn't kill the request.\n\n`shouldRetire` v1 only supports retire-and-retry semantics. There is no\n\"retire but let this response flow to the handler\" mode: Crawlee's\n`session.markGood()` call after a successful handler subtracts 0.5 from\n`errorScore`, undoing `Session.retire()`'s max-errorScore set. The\nretire decision would silently evaporate.\n\n### Identity binding is automatic (since 0.9.1)\n\n`GhostFetchHttpClient` reads the active Crawlee session directly from\n`request.sessionToken.id` — the `Session` object Crawlee binds to every\nclient request — and forwards its id as ghost-fetch's `session` arg. That\nbinds the Apify residential exit IP, cookie jar, and (via ghost-fetch\n0.4.0's session-engine recording) JA4 to the Crawlee session lifecycle.\nWhen `shouldRetire` retires a session, the next request carries a new\n`session.id` → new ghost-fetch session → fresh exit IP + empty cookie jar.\nNo hook, no wiring.\n\nIf no Crawlee session is bound (SessionPool off, or a non-Crawlee caller),\nthe client falls back to a static per-process token (one IP for the actor's\nlife).\n\n> **`ghostFetchPreNavigationHook` is deprecated since 0.9.1.** It only\n> copied `ctx.session.id` into `ctx.request.userData.sid`, which the client\n> still honors as a fallback — so existing wiring keeps working unchanged —\n> but the hook is now redundant. Drop it from `preNavigationHooks`. The\n> export remains for back-compat and will be removed in a future major.\n\nRecommended caller-side knobs:\n\n| Option | Recommendation | Why |\n|---|---|---|\n| `maxConcurrency` | `20` | Higher and ghost-fetch's session lock will serialize anyway |\n| `maxRequestRetries` | `3` (or `8` with `shouldRetire`) | One cold-start retry + headroom; bump for retire-heavy sites |\n| `navigationTimeoutSecs` | `120` | Cold-start browser warmup can take ~30-60s |\n| `requestHandlerTimeoutSecs` | `180` | Outer envelope above |\n| `keepAlive` | `true` | Standby actors are long-lived |\n| `retryOnBlocked` | `false` (don't set true) | Bypasses our `blockedStatusCodes: []` and reintroduces Crawlee's built-in `[401, 403, 429]` retire |\n\n## False-block recovery (`treatAsSuccess`)\n\nSome WAFs return an error status (typically `403`) **alongside the\nreal page body** as a deception layer. The wire-level status says\n\"blocked\", but the body contains exactly the data the actor wants —\nProduct JSON-LD, full listing tiles, etc.\n\nWithout intervention this round-trips badly through Crawlee:\n\n1. The HTTP client returns a response with `statusCode: 403` (the truth\n   from the wire).\n2. Crawlee's `CheerioCrawler` checks the status against\n   `blockedStatusCodes` (default `[401, 403, 429]`).\n3. On match, it calls `session.retire()` and **throws before the\n   request handler runs**.\n4. The request retries with a fresh session — fresh exit IP, fresh\n   empty cookie jar, fresh cold-warmup cost. The session that just\n   *succeeded* (the body was real) and the warm cookie jar tied to it\n   are both discarded.\n\n`GhostFetchHttpClient` exposes an opt-in predicate to recover from\nthis:\n\n```ts\nimport { GhostFetchHttpClient } from 'crawlee-ghost-fetch';\n\nnew GhostFetchHttpClient({\n    name: 'site',\n    defaultCountry: 'US',\n    treatAsSuccess: (p) => {\n        const c = p.response.body ?? '';\n        // Length floor rejects hard-block challenge bodies (~1 KB).\n        // Endpoint-specific marker rejects WAF blank-shell error pages\n        // (right outer shape, every field empty).\n        return c.length > 50_000\n            && /\"@type\":\\s*\"Product\"[^]*?\"name\":\\s*\"[^\"]+\"/.test(c);\n    },\n});\n```\n\nThe predicate receives the raw trace-v4 [`UnblockResult`](./src/types.ts) —\nread `p.response.body`, `p.verdict`, `p.usable_content`, etc.\n\n**Default behaviour (no predicate):** the client trusts ghost-fetch's own\nverdict — a `>=400` response the server judged usable (`usable_content: true`)\nis presented to Crawlee as `200`. `treatAsSuccess` **overrides** that judgement\nboth ways:\n\n- returns `true` for a `>=400` response → `statusCode` rewritten to `200`\n  (SessionPool keeps the session, handler runs), `statusMessage` becomes\n  `OK (upstream <orig>)`, and the original status is preserved at\n  `response.headers['x-ghost-fetch-upstream-status']`.\n- returns `false` → the upstream status flows straight through, even if\n  ghost-fetch judged the body usable (force a real block).\n\n### How to design the predicate\n\nTwo components, both required:\n\n1. **Body-length floor.** Hard-block challenge bodies are tiny —\n   500 B to 5 KB depending on the WAF. Real pages are 50+ KB. A floor\n   of ~30–50 KB rules out the obvious hard-block class without thinking.\n\n2. **Endpoint-specific success marker.** This is the load-bearing\n   check. Length alone false-passes WAF *blank-shell* error pages —\n   pages that look like the right shape but render every field empty.\n   Pick a marker that's only present when the data is real:\n\n   - **Product detail page**: Product JSON-LD with a *non-empty* name.\n     The blank-shell page has `\"name\": \"\"`, so `\"name\":\\s*\"[^\"]+\"` is\n     enough. Don't just match `\"@type\":\\s*\"Product\"` — the shell has\n     that too.\n   - **Listing / search results**: at least one product card / tile\n     element from your Phase 2 selector. Challenge and shell pages\n     don't render the card grid.\n   - **Detail page with API-loaded data**: a bound DOM attribute that\n     only the real-data path produces (`data-product-id=\"…\"` matching\n     a non-empty value, etc.).\n   - **JSON API**: if the API returns `application/json`, parse it\n     and check for the expected envelope key. Don't string-match —\n     valid API errors often have the same outer shape.\n\n### When an actor serves multiple endpoints\n\nDispatch off the URL inside the predicate. The predicate receives the\nfull payload but not the request URL; pull it from your config or\nkeep markers permissive (any marker satisfies):\n\n```ts\ntreatAsSuccess: (p) => {\n    const c = p.response.body ?? '';\n    if (c.length < 30_000) return false;\n    if (/\"@type\":\\s*\"Product\"[^]*?\"name\":\\s*\"[^\"]+\"/.test(c)) return true;\n    if (/data-tile-id=\"[^\"]+\"/.test(c)) return true;\n    return false;\n},\n```\n\nIf the actor talks to a mix of WAF'd and unprotected hosts (e.g. main\nstorefront + a CDN-hosted reviews API), the predicate is only consulted\nwhen status ≥ 400, so unprotected hosts (which return 200) bypass it\nnaturally.\n\n### What this is NOT\n\n- **Not a substitute for retry policy.** Real blocks still need\n  rotation. The predicate is a discriminator between \"the body has\n  what we asked for despite the status code\" and \"the body is a\n  challenge / error page\". Get the predicate wrong on the\n  conservative side and a real block flows through to the handler →\n  handler crashes / returns nulls → actor loses a retry budget. Get\n  it wrong on the permissive side and SessionPool hangs on to a dead\n  session → every subsequent request through that session fails.\n\n- **Not a replacement for the warmup hook.** A WAF that demands cookies\n  before serving content still needs `warmupFromUrl`. The predicate\n  only handles cases where the warmup *worked* but the WAF is\n  cosmetically masking success.\n\n- **Not free.** The predicate runs on every response. Keep it\n  regex-based and small — don't parse the full DOM.\n\n## Environment variables\n\n| Var | Required | Default | Purpose |\n|---|---|---|---|\n| `GHOST_FETCH_URL` | yes (or pass `ghostFetchUrl` on the client) | — | Points at your ghost-fetch deployment, e.g. `https://<user>--ghost-fetch.apify.actor`. The lib throws on construction if neither this nor the constructor arg is set. |\n| `APIFY_TOKEN` | yes when ghost-fetch is the Apify standby (Apify-hosted actors auto-inject) | — | Apify auth for the standby HTTP frontend in front of ghost-fetch. Set explicitly only for local dev or non-Apify hosting. |\n| `GHOST_FETCH_SESSION` | no | `${name}.{uuid}` per process | Pin a session token across process restarts. Rarely useful in production — `SessionPool` drives rotation. |\n\n## Cold-start behavior\n\nThe first request through the client triggers ghost-fetch's browser\ntier (a `*_browser` strategy — `chromium_browser` / `firefox_browser`)\nto mint a validated session cookie set — typically 20-40s wall clock.\nEvery subsequent request in the same process reuses that session token,\nso ghost-fetch routes them through its Tier 1 impit path (`impit_chrome`\n/ `impit_firefox`) with the warmed cookies — typically 1-3s. The winning\ntier is reported per attempt in the response's `decision_trace`\n(`strategy_id` + `transport`).\n\nFor an actor's first user-facing call to succeed in spite of the cold\nstart, the standard Crawlee retry config (`maxRequestRetries: 3`)\nbridges the gap: if the cold call returns a browser-tier 403 (Akamai\nsometimes rejects the very first request even with valid sensor data),\nthe retry sees freshly-banked cookies and lands on Tier 1.\n\n## Apify deployment notes\n\nThe Apify-hosted actor build runs `npm install` against the public\nnpm registry — no extra setup. `APIFY_TOKEN` is auto-injected at\nruntime for any Apify-hosted actor. Set `GHOST_FETCH_URL` on the\nactor (Settings → Environment variables) pointing at your ghost-fetch\nstandby.\n\n## Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| `crawlee-ghost-fetch: ghostFetchUrl is required` thrown on first request | Neither `ghostFetchUrl` config nor `GHOST_FETCH_URL` env is set | Pass one. There is no default. |\n| First call times out (>120s) | Cold-start browser warmup ran into an interactive challenge (Turnstile / hCaptcha) | Try a different `country` — Apify's residential pools vary; CZ/DE pools often clean for EU sites |\n| Every call resolves via a `*_browser` strategy, never drops to `impit_*` (Tier 1) | Tier 1 fingerprint mismatch with the residential exit's geo | Likely a ghost-fetch issue — open a ticket. Confirmed working: locale-aware Accept-Language is set per-country |\n| First call returns 500 from your actor with `Request failed: ...` | Crawlee aborted on `application/octet-stream` content-type before handler ran | Confirm you spread `...ghostFetchCrawlerOptions()` into your crawler config |\n| `crawlee-ghost-fetch: APIFY_TOKEN env var (or apifyToken config) is required` | No token visible to the actor at runtime | Ensure `APIFY_TOKEN` is in the actor's runtime env (Apify auto-injects it for Apify-hosted actors, set it manually for local dev) |\n| Multi-country actor: every request goes to `defaultCountry` | `countryFromUrl` returning `undefined` for valid URLs | Log inside the resolver — common bug is checking `tld === 'de'` (lowercase) when the ToUpperCase comparison expects `'DE'` |\n| SessionPool keeps retiring sessions even when handlers extract data fine | WAF returns 4xx alongside the real page body; default `blockedStatusCodes` retires the session before your handler can confirm success | Set [`treatAsSuccess`](#false-block-recovery-treatassuccess) on the client with a length floor + endpoint-specific marker |\n| Cold call rate is fine but the actor's IP-quality lottery never settles — every request pays the cold warmup | Same as above: the warm session that minted real cookies was retired the moment it returned 4xx, so nothing in the pool ever ages | Same fix; verify by inspecting `response.headers['x-ghost-fetch-upstream-status']` in the handler — if the rewrite is firing, the same session id should appear across consecutive successful calls |\n\n## Migration from an inline ghost-fetch client\n\nIf your actor already has a hand-rolled `ghost-fetch-client.ts`, the\ndiff is roughly:\n\n```diff\n- import { GhostFetchHttpClient } from './utils/ghost-fetch-client.js';\n+ import {\n+     GhostFetchHttpClient,\n+     ghostFetchCrawlerOptions,\n+ } from 'crawlee-ghost-fetch';\n\n  const crawler = new CheerioCrawler({\n-     httpClient: new GhostFetchHttpClient(),\n+     httpClient: new GhostFetchHttpClient({ name: 'overstock', defaultCountry: 'US' }),\n-     additionalMimeTypes: ['application/octet-stream'],\n-     useSessionPool: false,\n+     ...ghostFetchCrawlerOptions(),\n      ...\n  });\n```\n\nThen delete `src/crawler/utils/ghost-fetch-client.ts` from your actor.\n\nFor multi-country actors that previously used a `preNavigationHook` to\nstamp `x-ghost-country` onto request headers, **drop the hook entirely**\n— `countryFromUrl` runs inside the client's `sendRequest`, no header\nround-trip needed.\n\n## Versioning\n\nThis package follows semver. The `0.x` line tracks breaking changes\nfreely; we plan to cut `1.0.0` once the API has stabilized across at\nleast three production actors.\n\n## See also\n\n- [ghost-fetch](https://github.com/yfe404/ghost-fetch) — the\n  unblocker this library talks to.\n- [Crawlee](https://crawlee.dev) — the framework this library plugs\n  into.\n- [Apify standby actors](https://docs.apify.com/platform/actors/development/programming-interface/standby) —\n  the deployment shape both reference actors use.\n","readmeFilename":"README.md"}