Togoder security

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

  1. POST your lockfile or manifest to /api/paid-scan.
  2. 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.
  3. Your client signs a USDC payment on Base and retries with an X-PAYMENT header.
  4. 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.

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.

GET/api/account

Access end date, claim code, Ko-fi payments and LLM token usage over the last 30 days.

GET/api/keys

List the account's keys (prefix only; full keys are never shown again).

POST/api/keys

Create a key. JSON body {"name": "CI"}. The response holds the key once.

DELETE/api/keys/:id

Revoke a key. The last active key cannot be revoked.

Free endpoints

Read cached data or estimate cost. These never run a scan.

GET/api/package/:name/:version

Cached scan results for one package.

GET/api/package/:name/ranges

Every 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.

GET/api/packages

Every 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.

POST/api/analyze

Which packages in a lockfile are cached and which need scanning.

POST/api/upload

Parse a lockfile, or resolve a package entered by name (ecosystem, packageName, packageVersion) with its dependency tree, and list the dependencies (htmx partial).

POST/api/estimate-cost

Download 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.