API reference
Scan from your own code
Run npx togoder-scan, or call one paid endpoint, paid per request with the
x402 protocol.
You only pay for files nobody has scanned before, or get monthly access with an API key.
Easiest: the CLI
From a project directory or a CI step, togoder-scan finds the lockfile, waits for the scan, reads .npm-scanner-ignore and exits 1 on findings at or above --fail-on (default high). No dependencies, Node.js 18.3+.
npx togoder-scan estimate # price only npx togoder-scan login # save an API key npx togoder-scan # scan ./package-lock.json (or yarn.lock, pnpm-lock.yaml, bun.lock) npx togoder-scan --sarif -o results.sarif
In CI, set NPM_SCANNER_KEY instead of running login. Fully cached lockfiles scan free without a key. The CLI doesn't sign x402 payments; use the HTTP API below for that.
How payment works
- POST your lockfile or manifest to
/api/paid-scan. - The server downloads every package and hashes each file. Files whose exact content was scanned before are free; the rest set the price, returned as
402 Payment Required. - Your client signs a USDC payment on Base and retries with an
X-PAYMENTheader. - The server verifies and settles the payment on-chain, then runs the scan and returns results.
If every file was scanned before, the results come back without a 402 and you pay nothing. A package version whose files changed since its last scan is re-scanned for the changed files only.
/api/paid-scan
Multipart upload with a file field. The file name decides how it's parsed, so keep the original name:
- npm:
package-lock.json,npm-shrinkwrap.json,yarn.lock,pnpm-lock.yaml,bun.lock,package.json - Python:
poetry.lock,uv.lock,Pipfile.lock,requirements*.txt,pyproject.toml - Rust:
Cargo.lock. Go:go.mod,go.sum. Ruby:Gemfile.lock. PHP:composer.lock
The binary bun.lockb is not supported; convert it with bun install --save-text-lockfile --frozen-lockfile --lockfile-only. File contents can also go in a packageLockJson field with the name in fileName.
Manifests with version ranges (package.json, requirements.txt, pyproject.toml) are scanned at the newest matching version, direct dependencies only. Packages outside npm appear in results with an ecosystem prefix, e.g. pypi:requests. Without payment, the first request returns 402 with the quote:
# returns 402 with the price
curl -X POST https://security.togoder.click/api/paid-scan \
-F "file=@package-lock.json"
A single package and its dependencies
Instead of a file, send ecosystem (npm, pypi, cargo, go, gem or composer), packageName and optionally packageVersion (empty means the latest release). The package and its full runtime dependency tree are scanned, resolved the way the installer would pick them:
curl -X POST https://security.togoder.click/api/paid-scan \
-H "Content-Type: application/json" \
-d '{"ecosystem":"pypi","packageName":"requests","packageVersion":"2.32.3"}'
To scan an exact list without resolving dependencies, upload a file named packages.list with one ecosystem:name@version per line (the npm: prefix is optional).
Fixed quotes
For large files, get the price without holding a request open: POST /api/quotes takes the same input as /api/paid-scan and answers 202 with a quoteId while packages download. Poll GET /api/quotes/:id until status is "ready", then call /api/paid-scan with quoteId (form field, JSON field or query). The 402 then asks exactly priceUsd, and nothing is downloaded before payment. Quotes are valid for an hour.
curl -X POST https://security.togoder.click/api/quotes -F "file=@package-lock.json" # {"status":"accepted","quoteId":"…","statusUrl":"/api/quotes/…"} curl https://security.togoder.click/api/quotes/<quoteId> # {"status":"ready","priceUsd":0.2,"billableFiles":42,…} curl -X POST "https://security.togoder.click/api/paid-scan?async=1" \ -H "Content-Type: application/json" -d '{"quoteId":"<quoteId>"}'
Long scans
Add ?async=1 to answer as soon as the scan starts (after payment settles): the response is 202 with a statusUrl. Poll GET /api/scan-jobs/:id: it returns {"status":"running","progress":{…}} until the scan finishes, then the same body a synchronous scan returns. Results are kept for an hour.
curl -X POST "https://security.togoder.click/api/paid-scan?async=1" ...
# {"status":"accepted","jobId":"…","statusUrl":"/api/scan-jobs/…"}
curl https://security.togoder.click/api/scan-jobs/<jobId>
Client libraries
npm install x402-fetch
npm install x402-axios
Example
import { wrapFetchWithPayment } from 'x402-fetch'; import { createWalletClient, http } from 'viem'; import { base } from 'viem/chains'; import { privateKeyToAccount } from 'viem/accounts'; import { readFile } from 'node:fs/promises'; // Set up wallet const account = privateKeyToAccount(process.env.PRIVATE_KEY); const walletClient = createWalletClient({ account, chain: base, transport: http() }); // Wrap fetch with the x402 payment handler const fetchWithPayment = wrapFetchWithPayment(fetch, walletClient); // Upload and scan const form = new FormData(); form.append('file', new Blob([await readFile('package-lock.json')]), 'package-lock.json'); const res = await fetchWithPayment('https://security.togoder.click/api/paid-scan', { method: 'POST', body: form, }); console.log(await res.json());
Findings & ignore lists
The response has results (per package: riskLevel, effectiveRiskLevel, ignoredCount, issues), a flat issues list and a summary (packagesScanned, packagesFailed, totalIssues, highSeverityCount, ignoredCount).
Every finding has a stable findingId like NPS-3F9A0C1B2D4E: a hash of the package name, the file's SHA-256, the finding type and line. It stays the same across rescans and across versions that ship the same file.
Send an ignore field with /api/paid-scan (form field, or a string or array in JSON) to set reviewed findings aside. One entry per line, # starts a comment that comes back as the reason:
# .npm-scanner-ignore NPS-3F9A0C1B2D4E # reviewed: documented telemetry NPS-0811DA3A5FA0@2.1.0 # only in this version left-pad@1.3.0 # every finding of this version @acme/internal-tool # every finding of this package
curl -X POST https://security.togoder.click/api/paid-scan -H "Authorization: Bearer $NPM_SCANNER_KEY" -F "file=@package-lock.json" -F "ignore=<.npm-scanner-ignore"
Matched findings get ignored: true, ignoredBy and ignoreReason; they don't count in totalIssues or highSeverityCount, and effectiveRiskLevel drops to the worst remaining finding. Ignoring only changes your response; stored results are untouched. A malformed list returns 400.
Monthly access & API keys
Create an API key, then support us on Ko-fi with the claim code
shown on the account page in your message. Each month paid gives a month of free scans from the payment date.
Send the key with /api/paid-scan and the server skips the 402 step:
curl -X POST https://security.togoder.click/api/paid-scan \ -H "Authorization: Bearer $NPM_SCANNER_KEY" \ -F "file=@package-lock.json"
X-API-Key: <key> works too. Access includes 100M LLM tokens per 30 days, counted
from what the model actually processed; files scanned before are free. Over the limit, scans return 429.
Without access, the request falls back to x402 and returns 402. An unknown or revoked key returns 401.
/api/accountAccess end date, claim code, Ko-fi payments and LLM token usage over the last 30 days.
/api/keysList the account's keys (prefix only; full keys are never shown again).
/api/keysCreate a key. JSON body {"name": "CI"}. The response holds the key once.
/api/keys/:idRevoke a key. The last active key cannot be revoked.
Free endpoints
Read cached data or estimate cost. These never run a scan.
/api/package/:name/:versionCached scan results for one package.
/api/package/:name/rangesEvery version of a package grouped into ranges by verdict (flagged, warning, clean, failed, not_scanned), with ecosystem-native and semver constraints and the flagged files traced across versions. Also GET /api/package-ranges?name=. Names may be scoped or prefixed (pypi:requests); ?registry=0 skips the registry lookup, ?prereleases=exclude drops prereleases.
/api/packagesEvery scanned package version with overall stats. Query q, risk (safe…critical or issues), limit (max 200), offset. Findings of one: GET /api/packages/:id/issues.
/api/analyzeWhich packages in a lockfile are cached and which need scanning.
/api/uploadParse a lockfile, or resolve a package entered by name (ecosystem, packageName, packageVersion) with its dependency tree, and list the dependencies (htmx partial).
/api/estimate-costDownload packages and count tokens for a quote (htmx partial). Answers right away with a partial that polls GET /api/estimate-cost/jobs/:id until the quote is ready.
Guarantees
- Server-side pricing. The price comes from the server's own token count. Clients cannot set it.
- Verified before work. Payment is verified and settled before any scan runs.
- Failures are explicit. Packages that couldn't be analyzed come back as
failed, never as safe.