Developers · app.slopgrade.ai/api

Grade it, deslop it, gate it — from your pipeline.

app.slopgrade.ai/api · v8
live
ways
4
rate limit
60/min
caps
50k · 25

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.

Quickstart

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, … }
1 · HTTP API

Grade a text per request

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 »).

POST/api/gradegrade a text → SlopResult JSON
POST/api/grade/batchup to 25 texts, per-item pass/fail against an optional maxScore gate
GET/api/healthstatus + formula version
GET/api/usagecheck your monthly included-word quota (used / limit / remaining) before you hit the wall · $0, read-only

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, retryable
3 · Developer gates

Fail a PR that ships slop — or leaks a tenant

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

actionProtect mainmake the slopGrade check *required* on your default branch — one click after connecting the GitHub App · needs the CI capability (402 otherwise)
GET/api/gate-configpin the threshold + formula version for your pipeline · config only, never content · $0, keyless
POST/api/ci/gradehosted grade for prose or code (`mode`: prose|code, or an `ext` like .ts), no vendoring · $0, keyless, no session
GET/api/openapi.jsonmachine-readable OpenAPI 3.1 spec (SDK generation / API tooling) · $0, keyless
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
4 · MCP — the agents' corner

A $0 quality gate inside your agent

stdio server, zero dependency, deterministic and local. Your agent grades its draft and regenerates if it is slop — no network, no inference cost.

toolgate_text{ text, maxScore? } → { pass, maxScore, worst, slopScore, grade, verdict, measurable, formulaVersion, hash }
toolgrade_text{ text, save? } → { slopScore, grade, verdict, measurable, formulaVersion, hash }
toolexplain_score{ text } → { slopScore, grade, verdict, measurable, formulaVersion, hash, contributions }
toolgrade_batch{ texts, save? } → { results }
toolci_verdict{ repo?, sha? } → { repo, scanned, pass, verdict, threshold, runsSummary }
toolrepo_gate_status{ } → { total, capped, counts, repos }
toolfirewall_findings{ repo } → { repo, scanned, gateLevel, gateEntitled }
POST/api/mcphosted HTTP MCP endpoint (Authorization: Bearer sg_…), free · hosted-word cap, unlimited with Firewall — same tools, no local server
// 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).

5 · Verifiable badge

Show it and prove it

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.

GET/r/[id]/badgeembeddable SVG (grade + score, colour = grade)
[![slopGrade A · 12](https://app.slopgrade.ai/r/ID/badge)](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.