CussCheck

API docs

Base URL: https://profanity-api.mike-tusa.workers.dev. JSON in, JSON out. English word list (obscenity englishDataset) with leetspeak, look-alike character, repeated letter and spaced/dotted letter handling. This is a word filter, not AI moderation, and it does not understand context.

POST /v1/check

Request body (JSON, at most 16,384 bytes):

FieldTypeNotes
textstring, requiredAt most 10,240 bytes (UTF-8).
censorboolean, default trueWhen false, censored is null.
allowstring[]Words never to flag (up to 50, each ≤ 40 chars, letters/spaces/hyphens/apostrophes). Matched against the canonical term (e.g. "shit") and the matched text. Allow wins over deny. Allowing a canonical term turns off every match of it: allow: ["cum"] clears "Cumming, Georgia" but also all other uses of that word, so use it only for that case.
denystring[]Extra words to flag on word boundaries, with the same leetspeak, look-alike and dotted-letter handling (same limits as allow).
curl -s https://profanity-api.mike-tusa.workers.dev/v1/check -H 'content-type: application/json' -H 'x-api-key: pf_…' \
  -d '{"text":"well f.u.c.k that sh1t, jerk","deny":["jerk"]}'

Response:

{
  "ok": true,
  "has_profanity": true,
  "match_count": 3,
  "terms": ["fuck", "shit", "jerk"],
  "matches": [
    { "term": "fuck", "match": "f.u.c.k", "start": 5, "end": 12, "source": "dataset", "obfuscated": true },
    { "term": "shit", "match": "sh1t", "start": 18, "end": 22, "source": "dataset", "obfuscated": false },
    { "term": "jerk", "match": "jerk", "start": 24, "end": 28, "source": "deny", "obfuscated": false }
  ],
  "censored": "well ******* that ****, ****",
  "truncated": false,
  "text_bytes": 28,
  "engine": "obscenity@0.4.6 (englishDataset)",
  "version": "0.1.4"
}

start is inclusive and end exclusive, in JavaScript string (UTF-16 code unit) indices of your original text, so text.slice(start, end) returns the match. The censored copy replaces each matched code unit with *, so it has the same length as the input and the same positions apply. obfuscated: true means the match was found after joining spaced or dotted single letters (e.g. f.u.c.k, s h i t) or skipping hidden characters (soft hyphen, zero-width and other invisible format characters, combining marks), or on a masked fu*k-style spelling.

The real response is compact JSON (shown indented here). matches lists at most 500 entries in text order; when there are more, truncated is true. match_count and terms always cover every match, and censored always censors all of them.

POST /v1/keys

Issues a free API key: 1,000 requests/day, 60/min, commercial use allowed. No email or sign-up needed. The key is shown once; we store only its SHA-256 hash. Up to 3 keys per IP per day (approximate: the counter is eventually consistent, so parallel requests can occasionally get one more). Lost a key or need it revoked? Email mike.tusa@gmail.com

curl -s -X POST https://profanity-api.mike-tusa.workers.dev/v1/keys -H 'content-type: application/json' -d '{}'

Send it as X-API-Key. GET /v1/usage with the header shows today's usage.

Limits and headers

Errors

Errors look like {"ok":false,"error":{"code":"…","message":"…"}}. Codes: invalid_json, missing_text, invalid_censor, invalid_allow, invalid_deny (400); invalid_api_key, missing_api_key (401); cross_origin_forbidden (403); not_found (404); method_not_allowed (405); body_too_large, text_too_large (413); unsupported_media_type (415); rate_limited, ip_daily_quota, key_quota_exceeded, issuer_quota_exceeded, key_issue_limit, daily_cap_reached (429); internal_error (500); not_configured, unavailable (503).

Other endpoints

GET /health returns the service status and version. GET /privacy is the privacy note.

Limitations

Support

Questions, key revocation or abuse reports: mike.tusa@gmail.com.

Pricing

Free

$0

1,000 requests/day with a free API key (60/min). Commercial use allowed. No card, no sign-up form.

Without a key: 200/day and 10/min per IP.

Starter (coming soon)

~$9/mo

About 100,000 requests/month.

Pro (coming soon)

~$29/mo

About 1,000,000 requests/month, plus saved custom allow/deny lists.

Paid tier prices are estimates and not available to buy yet. Per-request allow/deny lists (up to 50 words each) work on every tier, including free.