slopcop

No keys. No server. Open CORS.

slopcop API

slopcop.me serves its rule catalog and report format as static JSON, plus a JavaScript module that runs the same WebAssembly linter as the demo. Scans run in your browser or Deno process. Your code goes to the repository host, never to slopcop.me.

Base URL
https://slopcop.me/api/v1/
Auth
None. Every response sends Access-Control-Allow-Origin: *.
Versioning
Breaking changes ship under a new path, such as /api/v2/. Each deploy refreshes v1 with the current linter; version.json names it.

JSON endpoints

GET /api/v1/version.json

The slopcop version that built the current endpoints and module, and the number of rules it ships. Compare it with slopcop --version to see whether your CLI matches the site.

FieldTypeDescription
versionstringSemantic version, such as 0.4.0.
rule_countintegerNumber of entries in rules.json.
$ curl https://slopcop.me/api/v1/version.json
{
  "version": "0.4.0",
  "rule_count": 46
}
GET /api/v1/rules.json

Every rule as an array, ordered by ID. This is the catalog behind slopcop rules and slopcop explain, and the SARIF rule descriptors.

FieldTypeDescription
idstringRule ID: DEAD, VIBE, or TRAIL and three digits.
modulestringdeadweight, vibecheck, or papertrail.
descriptionstringShort name of the pattern.
default_severitystringerror, warning, or info. A repository's .slopcop.toml can change it.
default_confidencestringhigh, medium, or low.
messagestringThe message findings of this rule carry.
suggestionstringHow to fix a finding.
rationalestringWhy the pattern is a problem.
examplesstring[]Code or prose the rule flags.
false_positivesstringWhat the rule deliberately leaves alone.
$ curl -s https://slopcop.me/api/v1/rules.json | jq -r '.[] | "\(.id) \(.description)"'
[
  {
    "id": "DEAD001",
    "module": "deadweight",
    "description": "Empty exception handler",
    "default_severity": "error",
    "default_confidence": "high",
    "message": "Exception is swallowed without logging, rethrowing, or handling.",
    "suggestion": "Handle the failure, rethrow it, or document a narrow and intentional exception.",
    "rationale": "Silent exception handling hides failures and turns debugging evidence into an unexplained fallback.",
    "examples": ["except NetworkError:\n    pass", "catch (error) {}"],
    "false_positives": "A deliberately ignored exception with an explanatory body comment, …"
  },
  …
]
GET /api/v1/rules/{id}.json

One rule, with the same fields as an entry of rules.json. An unknown ID returns 404.

ParameterInDescription
idpathRule ID in upper case, such as VIBE013. Case matters.
$ curl https://slopcop.me/api/v1/rules/VIBE013.json
{
  "id": "VIBE013",
  "module": "vibecheck",
  "description": "…",
  …
}
GET /api/v1/report.schema.json

A JSON Schema (draft 2020-12) for the report that slopcop --format json prints and that scanFiles and scanRepository return. The site build checks a real report against it, so the schema and the linter cannot drift apart.

FieldTypeDescription
versionstringThe slopcop version that produced the report.
findings[].pathstringRepository-relative path.
findings[].locationobjectOne-based line, column, end_line, and end_column. Columns count Unicode scalar values.
findings[].rule_idstringLook it up with rules/{id}.json.
findings[].module, severity, confidencestringAs in the rule catalog, after the repository's configuration.
findings[].message, suggestionstringWhat is wrong and how to fix it.
findings[].evidencestring | nullThe matched source text.
findings[].observationstring | nullDetail for this finding, such as the phrases a prose rule counted.
summaryobjectscanned_files, scanned_commits, skipped_files, and findings counts.
$ slopcop . --format json > report.json
$ check-jsonschema --schemafile https://slopcop.me/api/v1/report.schema.json report.json

JavaScript module

JS /api/v1/slopcop.js

An ES module that loads the linter's WebAssembly build (about 1 MB) on first use. Import it from a module script or a module Worker in the browser, or from Deno with --allow-net --allow-import. Node cannot import modules from URLs; use the CLI there.

The linter runs synchronously once the files are in memory. Large repositories keep the thread busy for a moment, so call the module from a Worker if your page has to stay responsive.

import { scanFiles, scanRepository, rules, version } from "https://slopcop.me/api/v1/slopcop.js";
$ deno run --allow-net --allow-import scan.js
JS scanFiles(files, options?) → Promise<Report>

Scans files you already have in memory and resolves to a report. Paths are repository-relative, and they choose each file's language and whether it is scanned at all, just as in a checkout.

ParameterTypeDescription
filesobject | Map | iterablePath to contents, as a plain object, a Map, or [path, contents] pairs. Contents are a string, Uint8Array, or ArrayBuffer.
options.configstringThe text of a .slopcop.toml. Without it, a .slopcop.toml among the files applies; without either, the defaults do.

A .gitattributes among the files excludes the paths it marks linguist-vendored or linguist-generated. Binary files, unsupported types, oversized files, dependency directories such as node_modules, and ignored paths count toward summary.skipped_files.

const report = await scanFiles({
  "src/app.py": "try:\n    run()\nexcept Exception:\n    pass\n",
  "README.md": await (await fetch("/README.md")).text(),
});
for (const finding of report.findings) {
  console.log(`${finding.path}:${finding.location.line} ${finding.rule_id} ${finding.message}`);
}
JS scanRepository(repo, options?) → Promise<RepositoryScan>

Fetches a public repository from GitHub, GitLab, or Codeberg and scans it, as the demo does. The repository's own .slopcop.toml and .gitattributes files apply.

ParameterTypeDescription
repostringowner/name for GitHub, gitlab.com/group/project, codeberg.org/owner/name, or a web URL of any of them, including tree and commit URLs and an @ref suffix.
options.refstringBranch, tag, or commit. Overrides a ref in repo. Defaults to the default branch.
options.tokenstringA GitHub token, to raise GitHub's anonymous limit. It is sent only to api.github.com, never to other hosts.
options.onProgressfunctionCalled with { stage, done, total } as the scan resolves the ref, lists files, downloads them, and scans. done and total count downloads.
Result fieldTypeDescription
repostringNormalized name: owner/name on GitHub, host/path elsewhere.
refstring | nullThe ref you asked for, or null for the default branch.
shastringThe full commit SHA that was scanned.
reportReportSee the report schema.
htmlstringA standalone page, identical to slopcop --format html, with findings linked to the host.
noticesstring[]What the scan left out and why, such as files over the limits or failed downloads.
statsobjecttreeFiles, downloaded, failed, totalMillis, and scanMillis.
const scan = await scanRepository("https://codeberg.org/dnkl/foot", {
  onProgress: ({ stage, done, total }) => console.log(stage, total ? `${done}/${total}` : ""),
});
console.log(scan.sha, scan.report.summary);
console.log(scan.notices.join("\n"));
JS version() → Promise<string> · rules() → Promise<Rule[]>

The same data as version.json and rules.json, read from the WebAssembly build the module loaded. They always agree with the linter that scans your files.

const catalog = new Map((await rules()).map((rule) => [rule.id, rule]));
console.log(await version(), catalog.get("DEAD001").rationale);

Reference

Errors

The JSON endpoints return 404 for unknown paths. The module's functions reject with an Error whose code property names the cause; other errors, such as network failures, have no code.

codeThrown byMeaning
bad-configscanFilesThe configuration is not valid TOML or names an unknown option. The message says where. scanRepository ignores a broken repository config and adds a notice instead.
bad-filescanFilesA file's contents are not a string, Uint8Array, or ArrayBuffer.
bad-repositoryscanRepositoryrepo is not a repository name or a GitHub, GitLab, or Codeberg URL.
not-foundscanRepositoryThe repository or ref does not exist, or the repository is private.
rate-limitscanRepositoryGitHub's API limit is used up. The message says when it resets.
bad-tokenscanRepositoryGitHub rejected options.token.
host-limitscanRepositoryGitLab or Codeberg kept throttling after three retries.

Limits

The JSON endpoints are static files on GitHub Pages with no rate limit of their own. scanRepository downloads every file from the repository host, so the host's limits apply to the network it runs from, and it caps each scan to stay within them. Run the CLI on a clone for anything larger.

HostFiles per scanHost limit for anonymous clients
GitHub3,00060 API requests per hour, two per scan. File downloads do not count. A token raises the limit to 5,000 per hour.
GitLab400500 API requests per minute, one per file.
Codeberg1,0002,000 API requests per 10 minutes, one per file.

Every scan also stops at 60 MB of downloads, and files over the configured max-file-size (1 MB by default) are skipped before download where the host lists sizes. When GitLab or Codeberg throttles a scan, every request to that host pauses for the time the host asks, or a minute, and onProgress reports the wait.