Skip to content
Dokeo

Docs

REST API

Every endpoint for scan, batch, billing, and more.
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.

REST API

Base URL: https://api.dokeo.co. Every endpoint is a JSON POST or GET. Bearer-token auth, HMAC-verified, rate-limited.

Authentication

Pass your tenant API key as a Bearer token. Create one while signed in at /account/api-keys, which calls POST /api/v1/account/keys.

bash
# All requests need an Authorization header with a Bearer token
curl https://api.dokeo.co/api/v1/scan \
  -H "Authorization: Bearer dk_your_tenant_key" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Your blog post here...",
    "content_type": "blog"
  }'

Response shape

Every scan returns the same envelope. verdict is the worst of five severities, most to least severe: BLOCKER (ships never), FAIL, WARNING, OPTIMIZATION (a suggestion, never blocks), or PASS. scores.overall is 0-100. checks_detail[] has one row per check with its own severity, reasons, and metrics.

json
{
  "slug": "my-post",
  "verdict": "WARNING",
  "content_type": "blog",
  "word_count": 1245,
  "report": {
    "scores": {
      "overall": 87,
      "seo": 92,
      "aeo": 81,
      "geo": 88
    },
    "meta": {
      "pass_count": 6, "blocker_count": 0, "fail_count": 0,
      "warning_count": 1, "optimization_count": 0
    },
    "checks_detail": [
      {
        "check": "structure",
        "status": "PASS",
        "severity": "PASS",
        "reasons": ["1245 words, in target band"],
        "metrics": { "word_count": 1245, "h2_count": 6 }
      }
    ],
    "action_items": [
      { "priority": "recommended", "action": "Add an FAQ section for AEO" }
    ],
    "inline_issues": [],
    "evaluation": {
      "content_hash": "sha256:9a1f...",
      "decision": { "verdict": "WARNING", "overall": 87, "findings": [["aeo", "WARNING"]] },
      "decision_hash": "sha256:3f9c..."
    }
  }
}

Endpoints

POST/api/v1/scan

Score inline content. 1 credit per call.

FieldTypeNotes
contentstringRequired The post body
content_typestringDefault: blog One of 17 types
titlestringOptional Post title
slugstringOptional Post slug
primary_keywordstringOptional Main SEO keyword
strictnessstringDefault: balanced strict | balanced | permissive
POST/api/v1/scan/replay

Re-run an earlier scan of your workspace and check the decision is identical. Free. Returns match, the inputs that changed, and both outcomes. Dokeo does not keep your text, so send it again.

FieldTypeNotes
contentstringRequired The same text that was scanned
decision_hashstringRequired report.evaluation.decision_hash from the earlier scan
POST/api/v1/scan/url

Fetch + score a URL. SSRF-guarded (blocks private/loopback IPs).

FieldTypeNotes
urlstringRequired https URL
content_typestringDefault: blog
strictnessstringDefault: balanced
POST/api/v1/scan/blog

Score every post on a blog (discovered via RSS/sitemap).

FieldTypeNotes
urlstringRequired Blog index / RSS / sitemap URL
limitintDefault: 10 Max posts to scan
content_typestringDefault: blog
POST/api/v1/campaign/qa

Pre-send QA for an email sequence (1 to 25 emails). Runs the email checks on each message and says what it would block. Shadow mode (default) never holds a send for quality, so you can compare it with what you actually send for a few weeks. Dokeo never sends mail. Uses 1 credit per email. Returns send, action (send | hold | skip), would_block_steps and, per email, verdict, score, findings and a decision_hash you can replay. Recipients on your do-not-contact ledger are dropped from send_to in every mode. sequence_findings lists schedule problems (quiet hours, steps too close together or out of order) and steps whose voice drifts from the rest of the sequence; they hold a send in enforce mode only when they reach fail_on.

FieldTypeNotes
campaignstringRequired A name for the sequence
emailsobject[]Required Each has step, subject and body, and optionally send_at (ISO 8601)
modestringDefault: shadow shadow | enforce (enforce returns send: false when anything would block)
fail_onstringDefault: fail none | blocker | fail | warning | optimization
min_scorenumberDefault: 0 A score under this blocks, whatever the verdict
strictnessstringDefault: strict strict | balanced | permissive
footerstringOptional What your sender appends, such as the unsubscribe line. Scored with each email
recipientsstring[]Optional Up to 1000 addresses. Returns send_to, suppressed and invalid_recipients
timingobjectOptional recipient_timezone (IANA name), quiet_start_hour (20), quiet_end_hour (7), min_gap_hours (24). Applied to steps that have send_at
POST/api/v1/campaign/suppressions

Record a refusal on your do-not-contact ledger. Append-only: the wording is kept as you give it, a second entry for the same address keeps the first, and no model is involved. Plus-tags are ignored, so sam+news@acme.com is covered by sam@acme.com.

FieldTypeNotes
targetstringRequired An address, or @domain.com for a whole domain
reasonstringOptional Up to 200 characters
source_textstringOptional What they wrote, kept as evidence
GET/api/v1/campaign/suppressions

Everyone currently suppressed for your workspace, newest first, with the original wording.

POST/api/v1/campaign/suppressions/anonymise

For an erasure request: replace a suppressed address with its SHA-256 digest and clear the stored wording. The person stays suppressed without Dokeo holding their address. Needs a signed-in admin session.

FieldTypeNotes
targetstringRequired The address or domain to anonymise
POST/api/v1/campaign/suppressions/lift

Remove a suppression. Needs a signed-in admin session (API keys cannot) and a reason. The history stays.

FieldTypeNotes
targetstringRequired The address or domain to lift
reasonstringRequired Why it is safe to mail them again
POST/api/v1/builder/research

Gate 1: research keyword + N tools. LLM does web search + per-tool lookup.

FieldTypeNotes
primary_keywordstringRequired
audiencestringRequired e.g. 'B2B event teams'
tool_countintDefault: 6 2-15
house_productstringRequired Your brand to lead with
llm_providerstringDefault: auto auto | claude | gpt | mock
mockboolDefault: false Use fixtures, no LLM
POST/api/v1/builder/generate

Gate 2: write the draft. Returns draft_md + QA report + editorial score.

FieldTypeNotes
research_bundleobjectRequired Output of /builder/research
formatstringDefault: listicle listicle | comparison | alternatives | buyers_guide
llm_providerstringDefault: auto
mockboolDefault: false
GET/api/v1/billing/entitlement

Current tenant + tier + credit balance + usage this period.

POST/api/v1/checkout/create-session

Create a checkout session for a paid tier. Requires a signed-in session (Bearer JWT).

FieldTypeNotes
tierstringRequired growth | pro | scale
cyclestringDefault: monthly monthly | annual
GET/api/v1/catalog/engines

List the supported engines (SEO/AEO/GEO) and their check names.

POST/api/v1/admin_ext/auto_fix

One-click AI rewrite for a failed check (configured writing model). Requires a Growth+ plan; consumes from the rewrite allowance, never from scans.

FieldTypeNotes
contentstringRequired The post body
check_namestringRequired The failing check
reasonsstring[]Optional From check.reasons[]
metricsobjectOptional From check.metrics{}
GET/openapi.json

Full OpenAPI spec at https://api.dokeo.co/openapi.json. Use `dokeo openapi --out spec.json` to dump.

Errors

CodeMeaning
401Missing or invalid Bearer token
402Out of credits. Upgrade or wait for refill.
404Path or scan result not found.
422Validation error (missing field, wrong content type).
429Rate limit exceeded. Check X-RateLimit-Reset header.
502Upstream (payment or search providers) unavailable.
503Server missing required configuration.

Related