Content theft detection with exact-match search queries
The simplest reliable way to detect copied content is to search for your own sentences. Pick a few distinctive sentences from each page, run each one as an exact-match query in quotes, exclude your own domains, and every result that remains is a page containing your exact words. Run that on a schedule and you have a content theft monitor that needs no crawler and no index of the web.
Exact-match search is precise but incomplete; the table below shows what it catches and what it misses.
Why exact-match search works
Scraper sites and content farms usually republish text verbatim or with light edits, because rewriting at scale costs them money. Search engines index those copies like any other page. Google treats a phrase in double quotes as an exact match; its Refine Google searches help page documents quotes for exact words or phrases, - to leave a word out and site: to limit to a site or domain (checked 2026-09-17). The minus and site: combine: -site:example.com removes your own pages.
A sentence of a dozen specific words is effectively unique on the web. If a page other than yours contains it, that page almost certainly derives from your text, directly or through someone who copied you.
What this approach catches and misses
| Case | Detected? |
|---|---|
| Verbatim full-article copies | Yes, if indexed |
| Partial copies that keep some sentences intact | Yes, if a fingerprint sentence survives |
| Syndicated or licensed republication | Yes (you then allowlist it) |
| Light edits (synonyms swapped in some sentences) | Partly: more fingerprints per page raises the odds |
| Paraphrased or AI-rewritten copies | Rarely |
| Translations | No, unless you fingerprint in the target language |
| Copies not indexed by Google, or behind logins | No |
| Copied images or video | No: this is a text method |
Use it as a high-precision detector for the common case, and accept that rewritten theft needs different tools.
Step 1: choose fingerprint sentences
Fingerprint quality decides precision. Good fingerprints are:
- 8 to 15 words. Long enough to be unique, short enough that a lightly edited copy still keeps the full phrase. Very long quoted queries are more likely to be broken up by small edits.
- Specific. They contain proper nouns, numbers, product names or unusual word pairings.
- From the body. Titles and intros are the parts most often rewritten by copiers, and the parts most often quoted legitimately.
- Yours. Not a quotation from someone else, not a definition that many pages share, not a legal disclaimer, not a template sentence repeated across your own site.
- Spread out. One from the first third, one from the middle, one near the end. A partial copy then still hits at least one.
A simple selection heuristic you can automate:
import re
STOP = set("the a an and or of to in for on with is are was were be by as at it this that from".split())
def sentences(text):
return [s.strip() for s in re.split(r"(?<=[.!?])\s+", text) if s.strip()]
def score(s):
words = re.findall(r"[A-Za-z0-9'%-]+", s)
if not 8 <= len(words) <= 15 or '"' in s:
return 0
rare = [w for w in words if w.lower() not in STOP]
digits = sum(any(c.isdigit() for c in w) for w in words)
capitals = sum(w[0].isupper() for w in words[1:])
return len(rare) + 2 * digits + capitals
def fingerprints(body, per_page=3):
sents = sentences(body)[2:] # skip the intro
if not sents:
return []
thirds = [sents[i * len(sents) // per_page:(i + 1) * len(sents) // per_page] for i in range(per_page)]
picks = [max(part, key=score) for part in thirds if part]
return [p.rstrip(".!?") for p in picks if score(p) > 0]
Review the picks for your most valuable pages by hand once. Strip characters that commonly change when text is copied, such as curly quotes and trailing punctuation.
Step 2: build the queries
One query per fingerprint:
"the exact fingerprint sentence goes here" -site:example.com -site:blog.example.com
Add a -site: term for each domain you own and for known licensed partners you do not want to see again. Keep the exclusion list short; it is cheaper to filter known domains in code after the results come back, and it keeps the query readable in your logs.
Step 3: run the checks through the API
A single check:
curl -X POST https://api.answerline.dev/v1/monitor/google \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "\"median onboarding time fell from 41 to 12 days after the change\" -site:example.com",
"country": "US"
}'
The response’s result.organicResults[] lists every page Google showed, with position, title, link, displayedLink, snippet and date. For exact-match queries on unique sentences, the list is usually short or empty, and an empty list is the good outcome.
For a whole site, submit async tasks in batches of up to 500 with a webhook. Use the article id, fingerprint index and month in the idempotency key so a rerun of the job does not create duplicate tasks:
import hashlib, os, requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
OWN = ["example.com", "blog.example.com"]
def query_for(fp):
return f'"{fp}" ' + " ".join(f"-site:{d}" for d in OWN)
def build_tasks(articles, month):
for a in articles:
for i, fp in enumerate(a["fingerprints"]):
yield {
"taskType": "GOOGLE",
"payload": {"query": query_for(fp), "country": "US"},
"idempotencyKey": f"theft:{a['id']}:{i}:{month}",
"webhook": {"url": "https://hooks.example.com/theft"},
}
def submit(articles, month):
tasks = list(build_tasks(articles, month))
for i in range(0, len(tasks), 500):
r = requests.post(f"{API}/v1/async/task/batch", json=tasks[i:i + 500], headers=HEADERS, timeout=60)
r.raise_for_status()
Keep a table mapping each fingerprint to its article id, so the webhook handler can look up which page a hit belongs to. See async tasks and webhooks for the delivery details.
If you publish in several markets, run the same fingerprints with the country and hl for each market: a copy aimed at a regional audience may rank there and not in the US results.
Step 4: score the candidates
Every result that survives your domain filter is a candidate. Score before a person looks:
| Signal | How to read it | Weight |
|---|---|---|
Fingerprint in snippet |
Google highlighted the matched text: strong evidence | High |
| Several fingerprints from one article hit the same domain | Likely a full copy | High |
| Domain on your licensed or syndication allowlist | Expected; log and skip | Suppress |
| Domain is a known aggregator of quotes (forums, Q&A) | Possibly a short quotation with a link | Low |
| Page title matches your article title | Wholesale copy | Medium |
Page date earlier than your publish date |
Check who copied whom | Flag |
Group candidates by domain and article. A domain that matches all three fingerprints of 40 of your articles is a scraper operation and deserves one case, not 120 tickets.
from urllib.parse import urlsplit
def host(url):
return (urlsplit(url).hostname or "").lower().removeprefix("www.")
def candidates(article_id, fp, organic, allowlist):
for r in organic:
h = host(r["link"])
if h in OWN or h in allowlist:
continue
yield {
"article_id": article_id,
"domain": h,
"url": r["link"],
"title": r.get("title"),
"snippet_match": fp.lower()[:40] in (r.get("snippet") or "").lower(),
}
Step 5: confirm and collect evidence
Before any action, a reviewer should confirm the copy and record evidence:
- Open the candidate URL and confirm the copied passage is there, not only in the search snippet from an older version.
- Save a dated capture of the page. The search result’s
link,titleandsnippetand the task id are useful supporting records; requestinclude.htmlif you want the results page itself retained. - Record your original: its URL, publish date, and ideally a public archive capture that predates the copy.
- Check the license position: syndication agreements, guest-post terms, press kits and quotes with attribution are not theft.
- Note whether the copy links back, carries a canonical tag to your page, or names you. That changes the conversation.
Step 6: choose a response
Responses escalate from cheapest to most formal:
| Situation | Typical first response |
|---|---|
| Legitimate site, copied without permission, no attribution | Contact the site; ask for removal or attribution and a canonical link |
| Syndication partner copying beyond the agreement | Raise it with the partner contact under the agreement |
| Anonymous scraper site | Notify the hosting provider, and consider a search engine removal request |
| Copy outranking your original | Removal request, and review why your page is not the one shown |
For Google specifically, its DMCA page (checked 2026-09-17) explains that copyright removal notices are submitted through its legal troubleshooter, per product. It warns that you can be liable for damages if you materially misrepresent that material is infringing, notes that the affected site can file a counter notification, and says Google may forward notices to Lumen, which publishes them after removing certain personal information. Take that warning seriously: a notice sent against a licensed partner or a fair quotation is a real risk.
Whether a given copy infringes, and which remedies apply in your jurisdiction, are legal questions. Talk to counsel before formal notices at scale. This article is not legal advice.
Step 7: watch where copies surface in AI answers
Copied content does not only compete in blue links. If a copy is cited in Google’s AI Overview for a query your article targets, the scraper gets the citation instead of you.
For your key queries, request the AI Overview and compare cited domains with your candidate list:
{
"query": "how to reduce onboarding time",
"country": "US",
"include": { "aioverview": { "markdown": false } }
}
Read result.aioverview.citationPills[].domain and result.aioverview.sources[].url. A domain that appears both there and in your theft candidates is the highest-priority case. The AI Overview adds 2 credits to the Google Search task. For the broader picture of citations, see how AI engines choose citations and mentions vs citations.
Scheduling and cost
Copies tend to appear soon after publication, when the article is fresh in feeds and sitemaps, so weight checks toward new content:
| Content age | Cadence |
|---|---|
| Published in the last 30 days | Weekly |
| 1 to 12 months | Monthly |
| Older, still high-traffic | Quarterly |
| Older, low value | Once, then stop |
Credits: a Google Search async task is 3 credits; a sync call is 2 more; an AI Overview adds 2. Example for a site with 400 articles, 3 fingerprints each:
| Segment | Articles | Checks per month | Credits |
|---|---|---|---|
| New (weekly) | 20 | 20 × 3 × 4 = 240 | 720 |
| 1–12 months (monthly) | 180 | 540 | 1,620 |
| Older high-traffic (quarterly) | 60 | 60 | 180 |
| Total | 260 | 840 | 2,520 |
Check plan sizes on the pricing page. The free tier’s 500 credits a month covers about 166 checks, enough to fingerprint your top 50 articles and see what turns up.
Pitfalls
- Generic fingerprints. “In this article we will explain how to get started” matches thousands of pages.
- Fingerprinting quoted material. If you quoted a study, the study’s other quoters are not thieves.
- Forgetting your own properties. Staging hosts, AMP caches, translated subdomains and old domains all show up as “copies”.
- One ticket per URL. Group by domain; scrapers copy in bulk.
- Acting on snippets. Confirm the live page before any notice.
- Sending notices to licensed partners. Keep an allowlist and check it first.
- Believing a clean result means no copies. It means no indexed verbatim copies for those sentences in that market today.
Related
The same monitoring pattern protects brand assets in other ways: see typosquatting detection for lookalike domains, brand monitoring tools for the tool landscape, and the brand protection use case.
Run your first exact-match check from the quickstart or see the Google Search engine page.
Questions
How do I find out if someone copied my article?
Take two or three distinctive sentences from the article, search each as an exact phrase in quotes while excluding your own domain with -site:, and review the pages that come back. Automating that per article and per week turns it into a monitor.
Which sentences make the best fingerprints?
Mid-length sentences, roughly 8 to 15 words, that contain specific nouns, numbers or unusual phrasing and appear in the body rather than the title or intro. Avoid boilerplate, quotations from other sources and common phrases.
Will exact-match search find every copy?
No. It only finds copies that the search engine has indexed and chooses to show for that phrase, and it misses copies that were reworded or translated. Treat it as a high-precision detector for verbatim copies, not a complete census.
What can I do once I find a copy?
Check whether it is licensed or syndicated first. If it is not, common options are contacting the site, asking for attribution or a canonical link, notifying the host, or filing a copyright removal request with the search engine. For legal decisions, consult a lawyer; this article is not legal advice.
What does monitoring cost with this API?
Each Google Search async task costs 3 credits. Checking 3 fingerprints for each of 200 articles once a month is 600 tasks, or 1,800 credits.