A deterministic score (pinned formula v8, same text = same grade + reproducible hash) your code calls 5 ways: the HTTP API (grade + batch), the deterministic deslop brief, the developer gates for CI (slop + the tenant-isolation Firewall), an MCP server for your agent, and a verifiable badge. The recipe for the score is public — the paid gate's rules stay server-side.
30 seconds: create a key at app.slopgrade.ai/keys, then POST a text to `/api/grade` with `Authorization: Bearer sg_…`. No account yet? Grade for free, no key, at app.slopgrade.ai/method (or `POST /api/grade/preview`).
# 1 · create a key at app.slopgrade.ai/keys # 2 · grade a text:
curl -X POST https://app.slopgrade.ai/api/grade \
-H "Authorization: Bearer sg_your_key" -H "Content-Type: application/json" \
-d '{"text": "Your draft…"}'
# → { "slopScore": 62, "grade": "C", "measurable": true, … }Every grade is one POST — deterministic, reproducible, metered against your included words.
What the score is: a deterministic measure of writing form (eight surface signals), not a calibrated verdict on truth or authorship. The same text always yields the same number, but a confident fabrication can still score well — and wrapping slop in invented facts lowers it: gate style with it, do not fact-check with it. `measurable:false` means the text was too short for a reliable signal, not « zero slop ». Use app.slopgrade.ai, not api.slopgrade.ai (that host is the database — it answers 401 « No API key found »).
Authentication: grading needs an account. Create a key at app.slopgrade.ai/keys and pass it as an Authorization: Bearer sg_… header (or x-api-key). No header → 401. Your monthly included words meter against the key too.
API-key scopes: every key is minted with a scope, and a call outside it returns 403 `scope_denied` (not a 401) — so widen the scope, do not re-issue over a 401. `grade` covers grade + batch; `rewrite` the deslop brief; `ci` the hosted CI grade; `mcp` the hosted MCP server; `full` covers all of them. Pick the narrowest scope that works and widen it at app.slopgrade.ai/keys; a broad endpoint hit with a narrow key fails closed.
Key management is dashboard-only (by design): create, rotate and revoke keys at app.slopgrade.ai/keys. There is no key-minting API — a key cannot create or revoke keys (a privilege-escalation guard). Pre-flight your remaining quota with `GET /api/usage`.
Rate limit: 60 requests per minute per IP. Past that the API answers 429 and you should retry after a minute — it is a burst guard, not a quota: your included words are metered separately.
Limits: `/api/grade` refuses a text over 50 000 characters with a 413 — never truncated. Batch takes 25 texts and trims each to 50 000, returning `received` / `truncated` so the cap never drops a piece silently. The hosted MCP and `/api/ci/grade` cap at 20 000.
Optional body params: `save` — an API key does NOT write to your history by default (send `save:true` to opt in; a session does save); `maxScore` — returns a pass/fail gate verdict. Batch also takes `authors` (per-piece attribution → the `byAuthor` rollup), `batchName`/`clientId`, stops on the first quota/auth failure (later items come back `reason:"skipped-quota"`), and turns a bare-URL item into a fetched + extracted grade (needs the extract tier; `url-tier` / `url-unreachable` otherwise).
Grade the whole draft, not one sentence. The score measures slop density across a text — a single sentence rarely crosses a threshold even when it is pure buzzword, so a one-line request often comes back `pass`. Send the paragraph, the email, the section — the unit you actually ship. This is why an agent self-correcting should pass its full draft to `grade` / `rewrite` (`mode:"brief"`), then re-grade after applying the brief.
curl -X POST https://app.slopgrade.ai/api/grade \
-H "Authorization: Bearer sg_your_key" \
-H "Content-Type: application/json" \
-d '{"text": "…"}'{
"slopScore": 62,
"grade": "C",
"lang": "other",
"signals": { "nominalization": …, "burstiness": …, "concreteness": …, "longword": …, "redundancy": …, "diversity": …, "entropy": … },
"markerDensity": …,
"contributions": [ { "key": "nominalization", "label": "…", "weight": …, "slopness": …, "points": … }, … ],
"sentences": [ { "text": "…", "level": "ai" }, … ],
"verdict": "…",
"formulaVersion": "v8",
"hash": "0x…",
"measurable": true
}curl -X POST https://app.slopgrade.ai/api/grade/batch \
-H "Authorization: Bearer sg_your_key" \
-H "Content-Type: application/json" \
-d '{"texts": ["First draft…", "Second draft…"], "authors": ["Alice", "Bob"], "maxScore": 60}'{
"results": [
{ "preview": "First draft…", "words": 120, "ok": true, "grade": "B", "slopScore": 34, "topSignal": "burstiness", "author": "Alice", "pass": true },
{ "preview": "Second draft…", "words": 98, "ok": true, "grade": "D", "slopScore": 66, "topSignal": "redundancy", "author": "Bob", "pass": false }
],
"count": 2, "graded": 2, "received": 2, "truncated": 0, "avgScore": 50,
"diagnosis": { … }, // dominant signal + lever + A–F distribution (null if nothing measurable)
"byAuthor": [ { "author": "Bob", "count": 1, "avgScore": 66, "avgGrade": "D", "dominant": "redundancy", "worst": 66 }, … ], // grading by writer · worst-first · [] if no authors sent
"batchId": null, "batchName": null,
"gate": { "maxScore": 60, "pass": false, "failed": 1 } // present ONLY when you send maxScore
}
// a failed / skipped item: { "preview": …, "ok": false, "reason": "quota|auth|rate|error|not-measurable|skipped-quota|url-tier|url-unreachable", "pass": false }Error responses: every failure is a JSON `{ error }` carrying the HTTP status. The message text is localized — branch on the status and the machine fields (`upgrade_url`, `used`) plus the `error` value on a 402 (`paid-only`) / 403 (`scope_denied`), never on the human message string.
400 # malformed JSON, or "text" missing / not a string
401 # missing / invalid / revoked key, or no session
402 # Free monthly word quota spent → { "error": …, "upgrade_url": "/pricing", "used": 48213 }
402 # paid-only surface → { "error": "paid-only", "upgrade_url": "/pricing" }
403 # scope_denied — the key's scope does not cover this endpoint (widen it at /keys)
413 # text over 50 000 chars — refused, never truncated
429 # 60 requests / minute / IP burst guard — retry after ~60s
503 # quota / billing momentarily unreadable — fail-closed, retryableWire the gates into CI with one reusable Action — `uses: maxor-tech/slopgrade-firewall@<sha>`. Nothing to vendor: the deterministic engine ships with the Action, the score is computed in your runner, and your source never leaves it.
the slop layer — grades the changed files into an advisory A–F note (reported, never fails the check; the Firewall is the gate) · $0, keyless, deterministic
# .github/workflows/slop-gate.yml
name: slop-gate
on: { pull_request: {} }
jobs:
slop:
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC — proves the repo, zero secret (the Firewall needs it)
contents: read
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # required: the PR diff needs the base commit
- uses: maxor-tech/slopgrade-firewall@<commit-sha> # pin a full SHA, not a moving tag
with:
# the A–F note is advisory (reported, never blocks) — the Firewall is the gate
paths: "*.md *.ts *.py" # default "*.md"; add code globs to gate code too
firewall-mode: advisory # advisory (free, reports) | gate (blocks; public repo or paid slot)
format: sarif # text (default) | json | sarif → Action output 'results-file'Wiring it: give the job `permissions: { id-token: write, contents: read }` — the OIDC token proves the repo with zero secret; without it the Firewall can't prove the repo and no-ops (fails open — never blocks). Keep `actions/checkout` at `fetch-depth: 0` (the PR diff needs the base commit), and pin the Action to a full commit SHA, not a moving tag.
Non-PR pipelines: pass `files:` (an explicit space-separated list) instead of relying on the PR diff — required for `push` / `workflow_dispatch` / scheduled jobs. `paths:` filters the PR diff; `files:` overrides it.
Where findings surface: the check annotates the PR; `--report` (or the Action's key) posts the run to your A→F climate at app.slopgrade.ai/ci; and `format: sarif` writes a SARIF 2.1.0 file (Action output `results-file`) you feed to `github/codeql-action/upload-sarif` for the GitHub Security tab. `format: json` emits the raw results.
Non-GitHub CI (GitLab / Jenkins / CircleCI): the slop gate is portable — run `ci/slop-gate.ts` directly (keyless, `$0`) or `POST /api/ci/grade`. The tenant-isolation Firewall is GitHub-only — it proves the repo via GitHub OIDC; elsewhere, use the slop gate.
The tenant-isolation Firewall (the paid CODE product) rides the same Action via `firewall-mode: advisory|gate`. Advisory is free and universal — it reports cross-tenant and cross-class leaks on every repo, never blocking. `gate` blocks the build on a hard finding. Your source never leaves CI: the Action sends a structural fingerprint, the detection rules run server-side only, and only a verdict comes back — the rules never ship to the client.
Who gets to block (per repo, not per plan): advisory — every repo, free, reports but never blocks · public repo — the cross-tenant gate blocks for free (open source does not pay), the other packs stay advisory · paid slot — every pack blocks with full file:line findings, public or private. Buy a slot at app.slopgrade.ai/pricing and assign it to `owner/repo`; a `gate`-mode repo with no entitlement simply stays advisory (enforced server-side, so a build is never falsely blocked).
What the Firewall gates: cross-tenant isolation first, then a growing set of code-security classes — SQL & NoSQL injection, SSRF, XXE, CSRF, SSTI, command & path injection, ReDoS, open redirect, secret exposure, insecure deserialization, and supply-chain / IaC drift. We publish which classes are gated, never how each is detected — the rules are the moat.
node --experimental-strip-types ci/slop-gate.ts docs/*.md # → ✗ docs/pitch.md — D · 65/100 (exit 1) # → ✓ docs/readme.md — A · 20/100 (exit 0)
Generate a typed SDK: `/api/openapi.json` is a full OpenAPI 3.1 document — body schemas (`GradeRequest`, `SlopResult`, `BatchRequest`, `BatchResult`) plus a per-tool argument schema for every MCP tool under `x-mcp-tools`. Point any generator at it for a typed client with the Bearer scheme wired.
npx @openapitools/openapi-generator-cli generate \ -i https://app.slopgrade.ai/api/openapi.json \ -g typescript-fetch \ -o ./slopgrade-sdk
stdio server, zero dependency, deterministic and local. Your agent grades its draft and regenerates if it is slop — no network, no inference cost.
// mcpServers
{
"slopgrade": {
"command": "node",
"args": ["--experimental-strip-types", "/path/to/slopgrade/mcp/server.ts"]
}
}// mcpServers — hosted (Bearer key, streamable HTTP)
{
"slopgrade": {
"type": "http",
"url": "https://app.slopgrade.ai/mcp",
"headers": { "Authorization": "Bearer sg_your_key" }
}
}The hosted endpoint caps each text at 20 000 characters (tighter than the HTTP API's 50 000) and grade_batch at 10 texts per call (the HTTP batch takes 25). `/mcp` and `/api/mcp` are the same server.
The server also exposes two resources — `slopgrade://formula` (weights, bands, threshold) and `slopgrade://signals` (the eight signals) — and a prompt `tighten_draft` (the grade → cut → re-grade loop, ready to run).
An embeddable SVG badge linking to the receipt; the hash proves the grade has not moved. The moat: a transparent receipt no black-box detector can issue.
[](https://app.slopgrade.ai/r/ID)
Help: check service status at `GET /api/health` · no key yet? grade free at app.slopgrade.ai/method · create & manage keys at app.slopgrade.ai/keys.