--- name: nuget-package-scraper description: Read NuGet package registration data as structured records via the Apify Actor arman-bd/nuget-package-scraper. Returns the canonical package ID, resolved latest version, description, authors, SPDX licence expression, project links, dependency groups broken out per target framework, deprecation state with the recommended replacement, vulnerability advisories, listed flag, publish date, total version count and an optional full version history. Use for .NET dependency audits, licence compliance, target-framework compatibility checks, dead-package replacement and SBOM enrichment. Not for private or self-hosted feeds, download statistics, package search or the contents of a .nupkg. --- # NuGet Package Scraper: .NET Packages & Frameworks Apify Actor `arman-bd/nuget-package-scraper`. Give it a list of package IDs, get one dataset record per package. It runs without credentials, and a latest-version lookup costs two requests per package however many hundreds of versions that package has. ## When to use it - Auditing a `.csproj` or `packages.lock.json` for licence, deprecation and advisories. - Answering "does this package support net8.0, and what does it pull in there?". - Finding the maintainer's own recommended replacement for a dead package. - Release-cadence and version-count analysis across a portfolio of packages. - Enriching an SBOM with flat JSON that joins on the canonical package ID. ## When not to use it - Private, self-hosted or organisation feeds. Only the public gallery is read. - Download counts or popularity ranking. Those are not in the registration data. - Searching for packages by keyword. You must already know the IDs. - Inspecting the contents of a package. You get a link to the `.nupkg`, not its files. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/nuget-package-scraper').call({ packages: ['Newtonsoft.Json', 'Serilog', 'Dapper'], includeAllVersions: false, includePrerelease: false, targetFramework: 'net8.0', }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); ``` One-shot over HTTP, when you want the rows back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~nuget-package-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"packages":["Newtonsoft.Json","Serilog"],"includeAllVersions":false,"includePrerelease":false,"targetFramework":"net8.0"}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/nuget-package-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `packages` | string[] | yes | | Package IDs, case-insensitive, or full gallery package URLs from which the ID is extracted. Entries containing anything outside letters, digits, dot, underscore and hyphen are dropped. Duplicates are removed. | | `includeAllVersions` | boolean | no | `false` | Populate the `versions` array with every published release. This walks every registration page, so a package with a long history costs roughly ten extra requests instead of one. | | `includePrerelease` | boolean | no | `false` | Let prerelease builds compete for `latestVersion` and appear in `versions`. Off means the newest stable release wins. | | `targetFramework` | string | no | `""` | Case-insensitive exact match on a TFM string, for example `net8.0`, `netstandard2.0` or `.NETFramework4.7.1`. Empty returns every dependency group. | **`includeAllVersions` is the whole cost model.** Off, the Actor walks registration pages newest first and stops at the first page holding a usable version, which is one index plus one page whatever the package size. On, it walks all of them. `totalVersions` is correct either way, because the page counts are published in the index, so leave the flag off unless you genuinely need per-version publish dates. The other decision is `targetFramework`, which narrows `dependencyGroups`, `targetFrameworks` and `dependencyCount` together. A package with no group for that TFM comes back with all three empty, and that emptiness is itself the answer to the compatibility question, not an error. ## Output One record per package that resolved. | Field | Type | Notes | |---|---|---| | `id` | string | The canonical ID with NuGet's own casing, not yours. Join to `.csproj` references on this. | | `latestVersion` | string \| null | The resolved latest version under the current `includePrerelease` setting. | | `description` | string \| null | Full description from the catalogue entry. | | `summary` | string \| null | Short summary. Very often `null`; most packages only set `description`. | | `title` | string \| null | Display title. Also often `null`. | | `authors` | string[] | Split from the comma-separated author string and trimmed. `[]` when unset. | | `licenseExpression` | string \| null | SPDX expression such as `Apache-2.0` or `MIT`. `null` on packages that only ship a licence URL or file. | | `licenseUrl` | string \| null | Licence link. Present even on some packages with no expression. | | `projectUrl` | string \| null | Project homepage. | | `iconUrl` | string \| null | Package icon. | | `tags` | string[] | Tag list, empty strings removed. | | `dependencyGroups` | object[] | `[{ targetFramework, dependencies: [{ id, range }] }]` for the latest version only. A group with no dependencies means the TFM is supported with nothing extra needed. `targetFramework` is the literal `any` when the group declares none. | | `targetFrameworks` | string[] | Just the `targetFramework` values from `dependencyGroups`, in the same order. Filtered by `targetFramework` when that is set. | | `dependencyCount` | number | Total dependencies across the returned groups, after filtering. | | `isDeprecated` | boolean | `true` when the latest version carries a deprecation notice. | | `deprecationReasons` | string[] | Reason codes such as `Legacy` or `CriticalBugs`. `[]` when not deprecated. | | `deprecationMessage` | string \| null | The maintainer's own message. | | `alternatePackage` | string \| null | The package ID NuGet recommends instead. This is the maintainer's declaration, not a guess. | | `vulnerabilities` | object[] | `[{ advisoryUrl, severity }]` for the returned version. Usually `[]`, because advisories attach to specific affected versions. | | `isListed` | boolean | `false` when the version is unlisted, which means it is installable but hidden from search. | | `published` | string \| null | Publish timestamp of the returned version, with an offset rather than a `Z`. | | `totalVersions` | number | Every version ever pushed, prerelease and unlisted included. Correct regardless of `includeAllVersions`. | | `versions` | object[] | `[]` unless `includeAllVersions` is on. Each entry has `version`, `published`, `listed`, `deprecated` and `targetFrameworks`, sorted oldest to newest by a real SemVer 2 comparator. | | `packageContent` | string \| null | Direct `.nupkg` download URL. | | `url` | string | The package's gallery page. | | `scrapedAt` | string | Run timestamp, ISO 8601. | A real record for `Serilog`, dependency groups truncated: ```json { "id": "Serilog", "latestVersion": "4.4.0", "description": "Simple .NET logging with fully-structured events", "summary": null, "title": null, "authors": ["Serilog Contributors"], "licenseExpression": "Apache-2.0", "licenseUrl": "https://www.nuget.org/packages/Serilog/4.4.0/license", "projectUrl": "https://serilog.net/", "iconUrl": "https://api.nuget.org/v3-flatcontainer/serilog/4.4.0/icon", "tags": ["serilog", "logging", "semantic", "structured"], "dependencyGroups": [ { "targetFramework": "net10.0", "dependencies": [] }, { "targetFramework": ".NETFramework4.6.2", "dependencies": [ { "id": "System.Diagnostics.DiagnosticSource", "range": "[8.0.1, )" }, { "id": "System.Threading.Channels", "range": "[8.0.0, )" } ] } ], "targetFrameworks": ["net10.0", ".NETFramework4.6.2", ".NETFramework4.7.1", "net6.0", "net8.0", "…"], "dependencyCount": 7, "isDeprecated": false, "deprecationReasons": [], "deprecationMessage": null, "alternatePackage": null, "vulnerabilities": [], "isListed": true, "published": "2026-07-10T03:35:24.047+00:00", "totalVersions": 600, "versions": [], "packageContent": "https://api.nuget.org/v3-flatcontainer/serilog/4.4.0/serilog.4.4.0.nupkg", "url": "https://www.nuget.org/packages/Serilog", "scrapedAt": "2026-08-06T11:45:24.979Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under the key `RUN_SUMMARY`. **Read it.** It is where a partial run admits that it was partial. ```json { "packagesRequested": 3, "packagesSaved": 2, "packagesFailed": 1, "failures": [ { "package": "Newtonsoft.Jsonn", "error": "package \"Newtonsoft.Jsonn\" not found on nuget.org (404)" } ], "filters": { "includeAllVersions": false, "includePrerelease": false, "targetFramework": "net8.0" }, "finishedAt": "2026-08-06T11:45:25.310Z" } ``` `packagesSaved` short of `packagesRequested` is fully explained by `failures`, each entry carrying `package` and `error`. Two failure messages matter to an agent: a not-found means the ID is wrong or the package was unlisted entirely, and "no stable versions" means the package exists but has only prereleases, which a re-run with `includePrerelease: true` fixes. Note that `packagesRequested` counts IDs after normalisation and de-duplication, so it can be lower than the array you sent. When every package fails the Actor throws instead of finishing. ## Behaviour to plan around - **`dependencyGroups` describes the latest version only**, never the whole history. Even with `includeAllVersions` on, the per-version entries carry only `targetFrameworks`, not their dependency lists. - **An empty `targetFrameworks` under a filter is a real answer.** It means the package declares no dependency group for that TFM. Do not retry with a different spelling before checking the unfiltered list. - **A group with zero dependencies is not the same as no group.** `{ "targetFramework": "net10.0", "dependencies": [] }` means supported and self-contained. - **`totalVersions` and `versions.length` measure different things.** The first counts everything ever pushed; the second is what survived the prerelease filter, and it is `[]` when `includeAllVersions` is off. Never infer version count from the array. - **Version ordering is SemVer 2, not string order.** `4.4.0` outranks `4.10.0-beta`, and a prerelease never outranks its own release. Do not re-sort lexically. - **`isListed: false` is a live package.** Unlisted means hidden from search but still installable, and it frequently accompanies a deprecation. - **`vulnerabilities` empty is not proof of safety.** Advisories attach to specific affected versions, and the field reflects only the version returned. Cross-check against an advisory database Actor for a real audit. - **`published` carries an offset, not a `Z`.** It is a valid ISO 8601 instant but a different shape from `scrapedAt`; normalise before comparing. - **A missing package never aborts the run.** It lands in `failures` and the next package proceeds. Transient failures get three attempts with linear backoff; a not-found is final and is not retried. - **`id` casing comes from NuGet, not from you.** Passing `newtonsoft.json` returns `Newtonsoft.Json`. Key your join on the returned `id`. ## Recipes **Licence and deprecation audit across a solution.** One row per package, everything an audit needs, cheapest possible run. ```json { "packages": ["Newtonsoft.Json", "Serilog", "Dapper", "AutoMapper", "WindowsAzure.Storage"], "includeAllVersions": false, "includePrerelease": false, "targetFramework": "" } ``` Flag rows where `isDeprecated` is `true` and read `alternatePackage`, and group `licenseExpression` for the legal report. **Target-framework compatibility check.** The empty rows are the finding. ```json { "packages": ["Serilog", "Dapper", "Microsoft.Extensions.Logging"], "includeAllVersions": false, "includePrerelease": false, "targetFramework": "net8.0" } ``` Any package whose `targetFrameworks` comes back `[]` has no dependency group for that TFM. **Release cadence study.** Full history with prereleases, then diff `published` between consecutive `versions` entries. ```json { "packages": ["Serilog"], "includeAllVersions": true, "includePrerelease": true, "targetFramework": "" } ``` **Resolve a package that only ships prereleases.** Re-run the failures from a previous `RUN_SUMMARY` with the flag on. ```json { "packages": ["SomePreviewOnly.Package"], "includeAllVersions": false, "includePrerelease": true, "targetFramework": "" } ```