| name | wikipedia-citations |
| description | Master Wikipedia citations — CS1/CS2 templates, Wayback Machine archiving, dead link detection, bare URL expansion, citation maintenance, and reference validation |
| depends_on | ["wikimedia-api-access"] |
| license | MIT |
| compatibility | opencode |
| skill_discovery_hints | [{"keywords":["citation","reference","cite web","cite book","CS1","Wayback Machine"]},{"keywords":["dead link","archive URL","bare URL","Citoid","DOI","ISBN"]}] |
| last_verified | "2026-06-10T00:00:00.000Z" |
⚠️ User-Agent required: API calls below need a descriptive User-Agent header.
See the wikimedia-api-access skill for the correct format.
This skill assumes you already understand the anatomy of a Wikipedia article — see
wikipedia-page-anatomy for infoboxes,
categories, navboxes, and the basic reference section structure.
Reference: CS1 Citation Templates
CS1 (Citation Style 1) is the standard template family for inline citations on
English Wikipedia. Every template has required, recommended, and optional parameters.
Core Templates
| Template | For | Minimal Parameters |
|---|
{{cite web}} | Web pages | url, title, website, access-date |
{{cite news}} | News articles | url, title, newspaper, date |
{{cite book}} | Books | title, author/last, year, isbn |
{{cite journal}} | Academic papers | title, author, journal, volume, pages, doi |
{{cite magazine}} | Magazines | url, title, magazine, date |
{{cite encyclopedia}} | Encyclopedias | title, encyclopedia, year |
{{cite report}} | Reports | url, title, institution, date |
{{cite arXiv}} | Preprints | eprint, class, date |
{{cite bioRxiv}} | Biology preprints | biorxiv, date |
{{cite conference}} | Conference papers | url, title, book-title, conference, date |
{{cite thesis}} | Theses/dissertations | title, type (e.g. PhD), publisher, year |
{{cite patent}} | Patents | country, number, title, pubdate |
{{cite podcast}} | Podcast episodes | url, title, series, date |
{{cite interview}} | Interviews | last, first, subject-link, interviewer, title, date |
{{cite AV media}} | Videos, films | url, title, people, date, medium |
{{cite episode}} | TV/radio episodes | title, series, network, date, season, number |
{{cite map}} | Maps | url, title, publisher, date |
{{cite press release}} | Press releases | url, title, publisher, date |
{{cite sign}} | Signs, plaques | title, medium (e.g. bronze plaque), location, date |
{{cite speech}} | Speeches | url, title, event, location, date, speaker |
{{cite tech report}} | Technical reports | url, title, institution, number, date |
{{cite court}} | Court cases | litigants, vol, reporter, opinion, court, date |
{{cite legislation}} | Laws | title, legislature, date |
{{cite web source}} | Wikisource | url, title, wslink |
Common Parameters (Across All CS1 Templates)
| Parameter | Description | Example |
|---|
url | Full URL of the source | https://example.com/article |
title | Title of the source | The Great Breakthrough |
trans-title | English translation of title | La Grande Percée |
last / first | Author name | last=Einstein, first=Albert |
author-link | Wikipedia article about the author | Albert_Einstein |
date | Publication date | 5 June 2026 or 2026-06-05 |
year | Publication year (when full date unknown) | 2026 |
work / publisher / website / journal / newspaper | Publication name | The New York Times |
series | Series of which this is part | Lecture Notes in Physics |
volume | Journal volume | 42 |
issue | Journal issue number | 3 |
pages | Specific pages | 123–145 |
page | Single page | 42 |
at | Precise location (section, paragraph, etc.) | para. 5 |
doi | Digital Object Identifier | 10.1000/xyz123 |
pmid | PubMed ID | 12345678 |
pmc | PubMed Central ID | PMC1234567 |
isbn | International Standard Book Number | 978-0-262-52316-5 |
issn | International Standard Serial Number | 0040-165X |
oclc | OCLC WorldCat ID | 123456789 |
jstor | JSTOR stable URL ID | 123456 |
bibcode | Bibcode (astrophysics) | 1924PhRv...23..123B |
arxiv | arXiv ID | 1234.56789 |
s2cid | Semantic Scholar Corpus ID | 12345678 |
access-date | When you accessed the URL | 5 June 2026 |
archive-url | Wayback Machine archive URL | https://web.archive.org/web/... |
archive-date | Date of the archive snapshot | 5 June 2026 |
url-status | Status of the original URL | live, dead, unfit, usurped |
quote | Relevant excerpt from the source | "Einstein discovered..." |
language | Language (if not English) | fr, de, es |
format | File format (if not HTML) | PDF, DOC |
id | Free-form identifier | ABC-123 |
CS2 (Citation Style 2)
CS2 uses a single {{citation}} template instead of separate template types.
It accepts the same parameters as CS1:
<ref>{{citation |url= |title= |last= |first= |date= |publisher= |isbn=}}</ref>
Use CS2 when all your citations are similar in structure and you want a
uniform look. Use CS1 when citation types vary (web vs book vs journal)
and readers benefit from knowing the format at a glance.
SOP: Named References and Reuse
Always use named references for sources cited more than once:
<ref name="einstein1950">{{cite book |last=Einstein |title=My Theory |year=1950}}</ref>
The same fact later.<ref name="einstein1950" /> ← slash-close reuses the footnote
Auto-generate ref names from the author surname + year:
einstein1950 for Einstein (1950)
smith2019a, smith2019b for multiple Smith (2019) sources
SOP: Archive URLs and the Wayback Machine
Checking for Existing Archives
The Wayback Machine provides a JSON API to check if a URL has been archived:
import requests
headers = {"User-Agent": "MyBot/1.0 (user@example.com) ContentGapResearch"}
url = "https://archive.org/wayback/available"
params = {"url": "https://example.com/article"}
resp = requests.get(url, params=params, headers=headers, timeout=30)
data = resp.json()
if data.get("archived_snapshots"):
snap = data["archived_snapshots"]["closest"]
archive_url = snap["url"]
archive_date = snap["timestamp"]
else:
print("No archive found")
Saving a URL to the Wayback Machine
import requests
headers = {"User-Agent": "MyBot/1.0 (user@example.com) ContentGapResearch"}
save_url = f"https://web.archive.org/save/{target_url}"
resp = requests.post(save_url, headers=headers, timeout=30)
if resp.status_code == 200:
print("Page archived successfully")
Note: The save endpoint returns the archived page HTML. Rate limits apply —
be conservative with save requests.
Adding Archive Parameters to a Citation
Once you have an archive URL, add it to the citation template:
<ref>{{cite web
|url=https://example.com/original-article
|title=Original Article
|website=Example News
|access-date=5 June 2026
|archive-url=https://web.archive.org/web/20260605000000/https://example.com/original-article
|archive-date=5 June 2026
|url-status=live
}}</ref>
The url-status parameter controls behavior:
live (default) — original URL is working; show archive as backup
dead — original URL is dead; show archive as primary, original as secondary
unfit — original was never suitable (e.g., low-quality source); suppress it
usurped — domain was taken over; suppress original URL
Using archive.today
check_url = f"https://archive.is/https://example.com"
resp = requests.head(check_url, headers=headers)
if resp.status_code == 200:
print("Archived on archive.today")
SOP: Detecting and Handling Dead Links
Find Pages with Dead Links
The Action API can find pages tagged with dead link templates:
import requests
headers = {"User-Agent": "MyBot/1.0 (user@example.com) ContentGapResearch"}
params = {
"action": "query",
"list": "categorymembers",
"cmtitle": "Category:All articles with dead external links",
"cmlimit": "max",
"format": "json",
}
resp = requests.get("https://en.wikipedia.org/w/api.php", params=params, headers=headers, timeout=30)
data = resp.json()
pages = [p["title"] for p in data["query"]["categorymembers"]]
Check Links Programmatically
import requests
headers = {"User-Agent": "MyBot/1.0 (user@example.com) ContentGapResearch"}
def check_url(url: str, timeout: int = 10) -> dict:
"""Check if a URL is live and returns useful content.
Returns:
{"status": "live", "code": 200}
{"status": "dead", "code": 404}
{"status": "redirect", "code": 301, "location": "..."}
{"status": "error", "error": "connection refused"}
"""
try:
resp = requests.get(
url,
headers=headers,
timeout=timeout,
allow_redirects=False,
)
if resp.status_code in (200, 304):
return {"status": "live", "code": resp.status_code}
elif resp.status_code in (301, 302, 303, 307, 308):
return {
"status": "redirect",
"code": resp.status_code,
"location": resp.headers.get("Location", "?"),
}
else:
return {"status": "dead", "code": resp.status_code}
except requests.exceptions.RequestException as e:
return {"status": "error", "error": str(e)}
Dead Link Detection Workflow
For a full dead-link audit of a page:
- Parse the wikitext and extract all
<ref> tags with url= parameters
- Check each URL with
check_url() above
- For dead URLs, query the Wayback Machine for an existing archive
- If no archive exists, request one via the save endpoint
- Report results: live, dead+archived, dead+noarchive, redirect
SOP: Bare URL Expansion
Bare URLs (<ref>https://example.com</ref>) are strongly discouraged per
WP:BAREURL. Convert them to proper citations:
Manual Expansion: Common Patterns
<!-- Bare URL -->
<ref>https://www.nytimes.com/2026/06/05/science.html</ref>
<!-- Converted -->
<ref>{{cite news
|url=https://www.nytimes.com/2026/06/05/science.html
|title=New Scientific Discovery
|last=Johnson
|first=Mark
|date=5 June 2026
|work=The New York Times
|access-date=5 June 2026
}}</ref>
Automated Tools
- reFill — Expands bare URLs to full citations
by fetching page metadata. Runs on Toolforge.
- Citation Bot — Adds DOI metadata,
archive links, and fills in missing fields automatically.
- WP:REFLINKS —
Browser-based tool for expanding bare references.
curl -s 'https://refill.toolforge.org/ng/api/en.wikipedia.org/Article_Title' \
-H 'User-Agent: MyBot/1.0 (user@example.com) ContentGapResearch'
SOP: Citation Maintenance Templates
These templates flag citation issues for human review:
| Template | When to Use | Placement |
|---|
{{dead link}} | URL returns 404 or connection error | Inside <ref>, at end: <ref>...{{dead link}}</ref> |
{{failed verification}} | Source exists but does not support the claim | After the <ref> |
{{full citation needed}} | Citation is missing key details (page, date, publisher) | After the <ref> |
{{page needed}} | Source is cited but no specific page number given | After the <ref> |
{{bare URL}} / {{bare URL inline}} | URL is not wrapped in a citation template | After the bare URL |
{{better source needed}} | Source is weak, unreliable, or primary | After the <ref> |
{{verification needed}} | Claim may not be verifiable from the cited source | After the claim |
{{self-published source}} | Source is self-published | After the <ref> |
{{primary source inline}} | Source is primary, secondary preferred | After the <ref> |
{{medical citation needed}} | Medical claim needs a reliable medical source | After the claim |
{{third-party inline}} | Source is too close to the subject | After the <ref> |
When to add vs. when to fix: If you can immediately fix the issue (add a
missing parameter, find an archive), do it. If you cannot, add the maintenance
template so a human editor knows what's wrong.
SOP: Extracting and Analyzing Citations from a Page
Parse All Citation URLs from Wikitext
import re, requests
headers = {"User-Agent": "MyBot/1.0 (user@example.com) ContentGapResearch"}
def extract_citations(page_title: str, lang: str = "en") -> list[dict]:
"""Fetch page wikitext and extract all citation URLs and templates."""
domain = f"{lang}.wikipedia.org"
params = {
"action": "parse",
"page": page_title,
"prop": "wikitext",
"format": "json",
}
resp = requests.get(f"https://{domain}/w/api.php", params=params, headers=headers, timeout=30)
data = resp.json()
wikitext = data["parse"]["wikitext"]["*"]
citations = []
for match in re.finditer(r"\{\{(cite \w+)([^}]*)\}\}", wikitext, re.IGNORECASE):
template = match.group(1)
params_text = match.group(2)
url_match = re.search(r"\|?\s*url\s*=\s*([^|\n}]+)", params_text)
url = url_match.group(1).strip() if url_match else None
archive_match = re.search(r"\|?\s*archive-url\s*=\s*([^|\n}]+)", params_text)
archive_url = archive_match.group(1).strip() if archive_match else None
citations.append({
"template": template,
"url": url,
"archive_url": archive_url,
"raw": match.group(0)[:200],
})
for match in re.finditer(r"<ref>(https?://[^\s<]+)</ref>", wikitext):
citations.append({
"template": "bare URL",
"url": match.group(1),
"archive_url": None,
"raw": match.group(0)[:200],
})
return citations
SOP: Citoid Auto-Citation (REST API)
The Citoid service, deployed on all WMF wikis,
automatically fetches citation metadata from a URL, DOI, ISBN, PMID, PMCID, or QID.
It powers VisualEditor's auto-cite feature and is accessible via the REST API.
API Endpoint
GET https://en.wikipedia.org/api/rest_v1/data/citation/mediawiki/{url|doi|isbn|pmid}
GET https://en.wikipedia.org/api/rest_v1/data/citation/zotero/{url|doi|isbn|pmid}
/mediawiki format returns data structured for wiki citation templates
/zotero format returns raw Zotero JSON (more structured, better for automation)
Fetch Citation Metadata from a URL
import requests
headers = {"User-Agent": "MyBot/1.0 (user@example.com) ContentGapResearch"}
url = "https://en.wikipedia.org/api/rest_v1/data/citation/mediawiki/https://example.com/article"
resp = requests.get(url, headers=headers, timeout=30)
citations = resp.json()
if citations:
c = citations[0]
print(f"Type: {c.get('itemType')}")
print(f"Title: {c.get('title')}")
print(f"Authors: {c.get('author', [])}")
print(f"Date: {c.get('date')}")
print(f"Publisher: {c.get('publisher')}")
print(f"URL: {c.get('url')}")
Fetch from a DOI
resp = requests.get(
"https://en.wikipedia.org/api/rest_v1/data/citation/mediawiki/10.7554/eLife.32259",
headers=headers,
)
citations = resp.json()
c = citations[0]
Converting Citoid Data to a Citation Template
def citoid_to_template(citation: dict) -> str:
"""Convert a Citoid citation object to a CS1 template string."""
item_type = citation.get("itemType", "webpage")
url = citation.get("url", "")
title = citation.get("title", "")
date = citation.get("date", "")
access_date = citation.get("accessDate", "")
type_map = {
"journalArticle": "cite journal",
"book": "cite book",
"bookSection": "cite book",
"newspaperArticle": "cite news",
"magazineArticle": "cite magazine",
"webpage": "cite web",
"report": "cite report",
"thesis": "cite thesis",
"conferencePaper": "cite conference",
"patent": "cite patent",
"film": "cite AV media",
"podcast": "cite podcast",
"interview": "cite interview",
"map": "cite map",
}
template = type_map.get(item_type, "cite web")
parts = [f"{{{{{template}}}"]
if url:
parts.append(f" |url={url}")
if title:
parts.append(f" |title={title}")
if date:
parts.append(f" |date={date}")
if access_date:
parts.append(f" |access-date={access_date}")
authors = citation.get("author", [])
for i, (last, first) in enumerate(authors, 1):
if i == 1:
parts.append(f" |last={last} |first={first}")
else:
parts.append(f" |last{i}={last} |first{i}={first}")
if template == "cite journal":
journal = citation.get("publicationTitle") or citation.get("journalTitle", "")
if journal:
parts.append(f" |journal={journal}")
volume = citation.get("volume", "")
if volume:
parts.append(f" |volume={volume}")
issue = citation.get("issue", "")
if issue:
parts.append(f" |issue={issue}")
pages = citation.get("pages", "")
if pages:
parts.append(f" |pages={pages}")
doi = citation.get("DOI", "")
if doi:
parts.append(f" |doi={doi}")
if template == "cite news":
newspaper = citation.get("publicationTitle", "")
if newspaper:
parts.append(f" |newspaper={newspaper}")
if template == "cite book":
publisher = citation.get("publisher", "")
if publisher:
parts.append(f" |publisher={publisher}")
isbn = citation.get("ISBN", [""])[0]
if isbn:
parts.append(f" |isbn={isbn}")
publisher = citation.get("publisher", "")
if publisher and template == "cite web":
parts.append(f" |publisher={publisher}")
elif publisher and template not in ("cite book", "cite news"):
parts.append(f" |publisher={publisher}")
parts.append("}}")
return "\n".join(parts)
citation = citoid_to_template(c)
print(citation)
CLI with Citoid
curl -s 'https://en.wikipedia.org/api/rest_v1/data/citation/mediawiki/https://example.com' \
-H 'User-Agent: MyBot/1.0 ContentGapResearch'
SOP: Generating Citation Templates Programmatically
When you have source metadata (from a DOI API, ISBN lookup, or user input),
you can generate the wikitext citation template:
def generate_cite_web(url: str, title: str, website: str, access_date: str,
last: str = "", first: str = "", archive_url: str = "",
archive_date: str = "", url_status: str = "live") -> str:
"""Generate a {{cite web}} template string."""
parts = ["{{cite web"]
parts.append(f" |url={url}")
if last and first:
parts.append(f" |last={last} |first={first}")
parts.append(f" |title={title}")
parts.append(f" |website={website}")
parts.append(f" |access-date={access_date}")
if archive_url and archive_date:
parts.append(f" |archive-url={archive_url}")
parts.append(f" |archive-date={archive_date}")
parts.append(f" |url-status={url_status}")
parts.append("}}")
return "\n".join(parts)
def generate_cite_book(title: str, author: str, year: str,
publisher: str, isbn: str = "",
pages: str = "") -> str:
"""Generate a {{cite book}} template string."""
parts = ["{{cite book"]
parts.append(f" |title={title}")
parts.append(f" |author={author}")
parts.append(f" |year={year}")
parts.append(f" |publisher={publisher}")
if isbn:
parts.append(f" |isbn={isbn}")
if pages:
parts.append(f" |pages={pages}")
parts.append("}}")
return "\n".join(parts)
def generate_cite_journal(title: str, author: str, journal: str,
volume: str, pages: str, doi: str = "",
year: str = "") -> str:
"""Generate a {{cite journal}} template string."""
parts = ["{{cite journal"]
parts.append(f" |title={title}")
parts.append(f" |author={author}")
parts.append(f" |journal={journal}")
if year:
parts.append(f" |year={year}")
parts.append(f" |volume={volume}")
parts.append(f" |pages={pages}")
if doi:
parts.append(f" |doi={doi}")
parts.append("}}")
return "\n".join(parts)
Guardrails
❌ Never fabricate a citation
Every <ref> must point to a real, verifiable source. If you lack a source,
use {{citation needed}} instead. No exceptions.
❌ Never guess a URL
Do not construct URLs from assumed patterns (e.g., https://nytimes.com/...).
If you cannot verify a URL exists, do not include it.
❌ Never strip existing archive data
If a citation already has archive-url and archive-date, preserve them.
Adding a new archive is fine; removing one is not.
❌ Don't confuse work, publisher, website, and journal
These are not interchangeable. Using the wrong field can break automated
metadata extraction:
website → For {{cite web}} — the site name (e.g., BBC News)
newspaper → For {{cite news}} — the publication name
journal → For {{cite journal}} — the academic journal name
publisher → For {{cite book}} — the publishing house
⚠️ Respect Wayback Machine rate limits
The /save endpoint is resource-intensive. Limit to 1–2 save requests
per minute. Use the /available endpoint (read-only) freely.
⚠️ Don't archive every URL
Not every URL needs an archive. Prioritize:
- URLs from news sites that remove articles behind paywalls
- Government pages that may disappear after administration changes
- Academic preprints that may move to final published versions
- Personal websites and blogs (most ephemeral)
⚠️ Verify archives actually work
The Wayback Machine may return a "captured" status for a page that is
mostly empty (JavaScript errors, redirect loops). Spot-check the archive
URL in a browser before relying on it.
Tooling
🔧 CLI Scripts
🐍 Python Assets
📚 Reference Docs
Cross-References