Skip to content
Dokeo

Docs

CLI

Score content and wire the gate from your terminal.
What is a content quality gate?
dokeo.co/blog/what-is-a-content-quality-gate
WARNING
76overall
77SEO
86AEO
66GEO
Fix before it ships12 of 19 checks pass
internal_linkingNo contextual links found (internal or external)
Fix
slopquestion-then-answer pattern found 2 time(s)
Fix

We gate our own blog. Scored 17 October 2026 with the same engine you get, glossary rules, balanced strictness.

CLI

The same gate, in your terminal. Score content, audit a blog, and wire the gate into CI - all from dokeo scan.

Install

The CLI ships as the dokeo package on PyPI. Requires Python 3.9+. The published wheel has no third-party dependencies. dokeo-mcp-server is the same package: it speaks MCP on stdio and forwards tool calls to the API.

bash
# Install from PyPI
pip install dokeo

Commands in this package:

bash
dokeo login API_KEY
dokeo scan --content TEXT | --file PATH | --url URL
dokeo batch URL [--limit N] [--content-type TYPE]
dokeo types
dokeo history [--tenant TENANT] [--limit N]
dokeo replay --content TEXT | --file PATH --decision-hash HASH
dokeo whoami
dokeo pre-commit [FILES...]
dokeo-mcp-server

Authenticate

Create a key while signed in at /account/api-keys (POST /api/v1/account/keys). Then either set it inline or save it with dokeo login.

bash
# Save your API key to ~/.dokeo/config.json (one-time setup)
dokeo login dk_your_tenant_key_here

# Or pass it inline
export DOKEO_API_KEY="dk_..."

# Point at a self-hosted instance if you're running one
export DOKEO_API_URL="http://localhost:8000"

Score a piece of content

The scan command accepts content three ways: inline (--content), from a file (--file), or by fetching a URL (--url). Other flags: --title, --slug, --content-type (17 types, including ai_output and api_docs), --strictness, --primary-keyword, --format, --on-error, --timeout. Global flags: --api-key, --api-url, --version.

bash
# Scan inline content
dokeo scan --content "Most B2B event teams outgrow their first registration tool..."

# Scan a file
dokeo scan --file post.md --content-type blog --primary-keyword "event software"

# Scan a URL (server fetches + extracts)
dokeo scan --url https://yourblog.com/posts/foo --content-type blog

# Output raw JSON instead of the pretty report
dokeo scan --content "..." --format

Sample output (pretty-printed by default):

text
  VERDICT  FAIL
  SCORE    54/100
  WORDS    152

  SEO  65    AEO  60    GEO  38

  CHECKS
    FAIL  structure
      → 152 words is below hard floor 800
      → only 1 H2 sections, expected >= 3
    WARNING  aeo
      → no FAQ section detected
    PASS  readability
    PASS  claims

  ACTION ITEMS  (5)
    [critical] Add 650 more words to clear the structure hard floor
    [recommended] Add an FAQ section with 3+ Q&A pairs
    [recommended] Cite at least 2 authoritative sources

Scan a whole blog

dokeo batch <url> sends the URL to the API, which discovers posts and scores them. The command always prints JSON. Flags are the URL, --limit, and --content-type. There is no --format flag on batch.

bash
# Scan the first 25 posts on your blog.
# batch always prints JSON. There is no --format flag.
dokeo batch https://yourblog.com --limit 25 --content-type blog

Prove a verdict later

Each scan returns a decision_hash in report.evaluation. It is a hash of the exact text, the request settings, the engine version, its thresholds, the reference corpus and your workspace rules, plus the verdict, score and findings. Dokeo stores the hashes, never the text, so replay takes the text again, re-runs the engine and says whether the decision is identical. It costs no credit and only works for hashes your own workspace produced.

bash
# Every scan prints a decision hash. Keep it with the draft.
dokeo scan --file post.md
#   DECISION  sha256:3f9c...

# Later: prove the same text still gets the same decision.
# Exit 0 = identical, 1 = different, 2 = the call failed.
dokeo replay --file post.md --decision-hash sha256:3f9c...

A replay runs the engine that is live now. If the engine or one of your rules changed since the scan, the answer names what changed (for example tenant_ruleset); it cannot rebuild the old engine. Scans of a URL are not covered yet.

Inspect your account

whoami prints the JSON from GET /api/v1/billing/entitlement. It does not invent a balance. history lists recent scans (--tenant, --limit). types prints engines and their checks from GET /api/v1/catalog/engines.

bash
# Prints the JSON from GET /api/v1/billing/entitlement.
# The fields are whatever that endpoint returns for your key.
dokeo whoami

Gate a build

dokeo scan exits 0 whatever the verdict, unless you ask it to gate with --fail-on and/or --min-score. dokeo pre-commit takes the same two flags and defaults to --fail-on fail. Added in 3.2.0; upgrade with pip install -U dokeo.

bash
# Block when the verdict is FAIL or worse, or the score is under 80
dokeo scan --file post.md --fail-on fail --min-score 80
echo $?   # 0 passed, 1 blocked, 2 could not decide

# Same rule in a pre-commit hook (default --fail-on fail)
dokeo pre-commit post.md

# A small stable JSON summary for scripts
dokeo scan --file post.md --fail-on warning --verdict-json
ExitMeaning
0Passed the gate.
1Blocked: the verdict is at or past --fail-on, or the score is under --min-score.
2Dokeo could not decide: no API key, bad input, or an API error with --on-error block.

--fail-on takes blocker, fail, warning, optimization (the strictest: anything short of a clean pass blocks) or none. A score under --min-score always blocks, whatever the verdict. --verdict-json prints a small stable summary (dokeo.verdict/v1: verdict, score, blocked, decision_hash, findings) that a script can read.

On GitHub, the Action applies the same rule and comments on the PR.

Related