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 refreshesv1with the current linter;version.jsonnames it.
JSON endpoints
/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.
| Field | Type | Description |
|---|---|---|
version | string | Semantic version, such as 0.4.0. |
rule_count | integer | Number of entries in rules.json. |
$ curl https://slopcop.me/api/v1/version.json
{
"version": "0.4.0",
"rule_count": 46
}
/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.
| Field | Type | Description |
|---|---|---|
id | string | Rule ID: DEAD, VIBE, or TRAIL and three digits. |
module | string | deadweight, vibecheck, or papertrail. |
description | string | Short name of the pattern. |
default_severity | string | error, warning, or info. A repository's .slopcop.toml can change it. |
default_confidence | string | high, medium, or low. |
message | string | The message findings of this rule carry. |
suggestion | string | How to fix a finding. |
rationale | string | Why the pattern is a problem. |
examples | string[] | Code or prose the rule flags. |
false_positives | string | What 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, …"
},
…
]
/api/v1/rules/{id}.json
One rule, with the same fields as an entry of rules.json. An unknown ID returns 404.
| Parameter | In | Description |
|---|---|---|
id | path | Rule ID in upper case, such as VIBE013. Case matters. |
$ curl https://slopcop.me/api/v1/rules/VIBE013.json
{
"id": "VIBE013",
"module": "vibecheck",
"description": "…",
…
}
/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.
| Field | Type | Description |
|---|---|---|
version | string | The slopcop version that produced the report. |
findings[].path | string | Repository-relative path. |
findings[].location | object | One-based line, column, end_line, and end_column. Columns count Unicode scalar values. |
findings[].rule_id | string | Look it up with rules/{id}.json. |
findings[].module, severity, confidence | string | As in the rule catalog, after the repository's configuration. |
findings[].message, suggestion | string | What is wrong and how to fix it. |
findings[].evidence | string | null | The matched source text. |
findings[].observation | string | null | Detail for this finding, such as the phrases a prose rule counted. |
summary | object | scanned_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
/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
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.
| Parameter | Type | Description |
|---|---|---|
files | object | Map | iterable | Path to contents, as a plain object, a Map, or [path, contents] pairs. Contents are a string, Uint8Array, or ArrayBuffer. |
options.config | string | The 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}`);
}
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.
| Parameter | Type | Description |
|---|---|---|
repo | string | owner/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.ref | string | Branch, tag, or commit. Overrides a ref in repo. Defaults to the default branch. |
options.token | string | A GitHub token, to raise GitHub's anonymous limit. It is sent only to api.github.com, never to other hosts. |
options.onProgress | function | Called with { stage, done, total } as the scan resolves the ref, lists files, downloads them, and scans. done and total count downloads. |
| Result field | Type | Description |
|---|---|---|
repo | string | Normalized name: owner/name on GitHub, host/path elsewhere. |
ref | string | null | The ref you asked for, or null for the default branch. |
sha | string | The full commit SHA that was scanned. |
report | Report | See the report schema. |
html | string | A standalone page, identical to slopcop --format html, with findings linked to the host. |
notices | string[] | What the scan left out and why, such as files over the limits or failed downloads. |
stats | object | treeFiles, 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"));
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.
| code | Thrown by | Meaning |
|---|---|---|
bad-config | scanFiles | The 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-file | scanFiles | A file's contents are not a string, Uint8Array, or ArrayBuffer. |
bad-repository | scanRepository | repo is not a repository name or a GitHub, GitLab, or Codeberg URL. |
not-found | scanRepository | The repository or ref does not exist, or the repository is private. |
rate-limit | scanRepository | GitHub's API limit is used up. The message says when it resets. |
bad-token | scanRepository | GitHub rejected options.token. |
host-limit | scanRepository | GitLab 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.
| Host | Files per scan | Host limit for anonymous clients |
|---|---|---|
| GitHub | 3,000 | 60 API requests per hour, two per scan. File downloads do not count. A token raises the limit to 5,000 per hour. |
| GitLab | 400 | 500 API requests per minute, one per file. |
| Codeberg | 1,000 | 2,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.