Duplicate content checker
This duplicate content checker compares two pieces of writing with consecutive word shingles and reports a clear similarity percentage.
Run — free
It normalizes case and punctuation, builds a set of phrases from each text, and measures their overlap with the Jaccard coefficient. You can choose the shingle size and the percentage that should trigger a likely-duplicate flag. The result is deterministic, easy to audit, and useful for reviewing landing pages, product descriptions, syndicated articles, and other copy before publication.
What shingle similarity reveals
A shingle is a short run of consecutive words. With a shingle size of three, the sentence “write useful pages for readers” produces phrases such as “write useful pages,” “useful pages for,” and “pages for readers.” The checker creates these phrase sets for both texts, counts the phrases they share, and divides that count by the number of unique phrases across both sets. That set-based Jaccard calculation produces the similarity percentage. It rewards matching passages while ignoring capitalization and punctuation differences that do not change the words. This makes the result more informative than comparing individual word counts, because common vocabulary alone does not create a match unless the words occur in the same local order. The score is evidence of textual overlap, not a search-engine penalty prediction. A high value tells you that the submitted versions reuse many of the same phrases and deserve editorial review. A low value tells you their exact phrasing differs, although it cannot prove that either page is original, accurate, useful, or safe to publish.
Choose a shingle size and threshold
The default shingle size is three words, a practical starting point for ordinary web copy. Smaller shingles are more sensitive: they catch short shared fragments, but they can also count routine phrases that appear naturally across unrelated pages. Larger shingles demand longer verbatim sequences, so they reduce incidental matches while overlooking passages that were lightly rewritten. Choose the size before interpreting the percentage, because scores produced with different sizes are not directly comparable. The duplicate threshold controls only the returned likely-duplicate flag; it does not alter the underlying similarity calculation. A threshold of 70 means a score of 70 percent or higher is flagged. Teams should calibrate that value with examples from their own content library instead of treating one universal cutoff as an SEO rule. Keep the same settings when comparing batches so results remain consistent. Each submitted text must contain at least as many normalized words as the shingle size. If either one is shorter, the request fails instead of presenting an artificial or mathematically misleading percentage.
Turn the result into an editorial decision
Use the score as a screening signal in a review workflow. Start by comparing pages that target similar queries, templates that were populated from the same catalog, or a draft against the source material supplied to a writer. When the tool flags a pair, inspect both documents and identify why their shingles overlap. Boilerplate navigation, legal notices, specifications, and standard calls to action may be legitimate. Matching introductions, explanations, benefits, and conclusions are stronger reasons to revise. Preserve facts that must remain correct, but rewrite the organization and supporting language so each page serves its own reader intent. Record the shingle size and threshold beside the result if you need a reproducible audit trail. The checker processes exactly two texts per request and does not crawl URLs, search the public web, identify an original author, or decide which page a search engine will rank. For a site-wide review, select meaningful candidate pairs first, run them with consistent settings, and prioritize the highest scores for human examination rather than automatically deleting or redirecting pages.
What you can do with it
Review similar landing pages
Compare pages aimed at neighboring keywords and find passages that make the pages unnecessarily repetitive.
Check supplier product copy
Measure how closely a revised product description still follows the source text supplied by a manufacturer.
Screen editorial submissions
Compare a new draft with an existing article before publication and route high-overlap pairs to an editor.
FAQ
How is the similarity percentage calculated?
The checker uses unique consecutive-word shingles and calculates their Jaccard similarity: shared shingles divided by all unique shingles present in either text, multiplied by 100.
What happens when a text is shorter than the shingle size?
The request returns an invalid input error naming the short text. A valid comparison requires each text to contain enough words to form at least one shingle.
Does a likely-duplicate flag mean a search engine will penalize the page?
No. The flag reports phrase overlap at your chosen threshold. It does not predict indexing, canonical selection, ranking, or a search-engine action.
Does punctuation or capitalization affect the score?
No. The comparison normalizes Unicode text, letter case, and punctuation before building shingles. Word identity and order still affect the result.
What does the API request cost?
Each API request costs $0.002. The same deterministic check is also available free in your browser on this page.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/seo/duplicate-content-check \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text_a":"Search engines value useful original pages that answer a reader'\''s question clearly and completely.","text_b":"Search engines value useful original pages that answer a reader'\''s question clearly and completely."}'const res = await fetch("https://api.kit.forhosting.com/seo/duplicate-content-check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text_a": "Search engines value useful original pages that answer a reader's question clearly and completely.",
"text_b": "Search engines value useful original pages that answer a reader's question clearly and completely."
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/seo/duplicate-content-check",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text_a": "Search engines value useful original pages that answer a reader's question clearly and completely.",
"text_b": "Search engines value useful original pages that answer a reader's question clearly and completely."
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/seo/duplicate-content-check", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text_a":"Search engines value useful original pages that answer a reader\'s question clearly and completely.","text_b":"Search engines value useful original pages that answer a reader\'s question clearly and completely."}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text_a":"Search engines value useful original pages that answer a reader's question clearly and completely.","text_b":"Search engines value useful original pages that answer a reader's question clearly and completely."}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/seo/duplicate-content-check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text_a": "Search engines value useful original pages that answer a reader's question clearly and completely.",
"text_b": "Search engines value useful original pages that answer a reader's question clearly and completely."
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "seo.duplicate_content_check",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |