---
name: wikipedia-citations
description: Master Wikipedia citations — CS1/CS2 templates, Wayback Machine archiving, dead link detection, bare URL expansion, citation maintenance, reference validation, and structural reference errors (broken refs, duplicate names, CheckWiki 81)
depends_on: [wikimedia-api-access, wikimedia-wikitext]
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-08-10
---

> ⚠️ **User-Agent required:** API calls below need a descriptive `User-Agent` header.
> See the **[wikimedia-api-access](../wikimedia-api-access/SKILL.md)** skill for the correct format.

This skill assumes you already understand the anatomy of a Wikipedia article — see
**[wikipedia-page-anatomy](../wikipedia-page-anatomy/SKILL.md)** 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:

```wikitext
<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:

```wikitext
<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:

```python
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()
# data["archived_snapshots"]["closest"]["url"] is the archive URL if one exists
# data["archived_snapshots"]["closest"]["timestamp"] is the snapshot date

if data.get("archived_snapshots"):
    snap = data["archived_snapshots"]["closest"]
    archive_url = snap["url"]
    archive_date = snap["timestamp"]  # Format: YYYYMMDDHHMMSS
else:
    print("No archive found")
```

### Saving a URL to the Wayback Machine

```python
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:

```wikitext
<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

```python
# archive.today does not have a public JSON API.
# To check if a URL is archived on 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:

```python
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",       # Up to 500 per request
    "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

```python
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,  # Check redirects separately
        )
        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:

1. Parse the wikitext and extract all `<ref>` tags with `url=` parameters
2. Check each URL with `check_url()` above
3. For dead URLs, query the Wayback Machine for an existing archive
4. If no archive exists, request one via the save endpoint
5. 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

```wikitext
<!-- 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](https://refill.toolforge.org/)** — Expands bare URLs to full citations
  by fetching page metadata. Runs on Toolforge.
- **[Citation Bot](https://citations.toolforge.org/)** — Adds DOI metadata,
  archive links, and fills in missing fields automatically.
- **[WP:REFLINKS](https://en.wikipedia.org/wiki/Wikipedia:REFLINKS)** —
  Browser-based tool for expanding bare references.

```bash
# reFill can be called programmatically (Toolforge required):
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

> ⚠️ **Use mwparserfromhell, not regex.** Regex breaks on nested templates
> (e.g. `|url={{URL|...}}` truncates at the inner `}}`), multi-line params,
> and pipes inside wikilinks. See the **[wikimedia-wikitext](../wikimedia-wikitext/SKILL.md)**
> skill for the full anti-pattern list. `citation_linter.py` in this skill's
> `assets/` is the reference implementation.

```python
import mwparserfromhell as mw

def extract_citations(wikitext: str) -> list[dict]:
    """Extract all citation templates and bare URLs (AST, no regex)."""
    parsed = mw.parse(wikitext)
    citations = []

    # Pattern 1: Template-based citations ({{cite ...}} / {{citation}})
    for template in parsed.filter_templates():
        name = str(template.name).strip().lower()
        if not (name == "citation" or name.startswith("cite ")):
            continue
        params = {str(p.name).strip().lower(): str(p.value).strip()
                  for p in template.params if str(p.name).strip()}
        citations.append({
            "template": name,
            "url": params.get("url", ""),
            "archive_url": params.get("archive-url", ""),
            "has_identifier": any(
                k in ("doi", "pmid", "pmc", "isbn", "arxiv", "eprint")
                for k in params
            ),
            "raw": str(template)[:200],
        })

    # Pattern 2: Bare URLs inside <ref> tags
    for tag in parsed.filter_tags(
        matches=lambda t: str(t.tag).strip().lower() == "ref"
    ):
        if tag.contents is None:
            continue
        content = str(tag.contents).strip()
        if content.startswith(("http://", "https://")):
            citations.append({
                "template": "bare URL",
                "url": content,
                "archive_url": "",
                "has_identifier": False,
                "raw": str(tag)[:200],
            })

    return citations
```

---

## Reference: Reference Groups (`group=`) and Notes

The Cite extension's `group` attribute partitions refs into **separate
footnote namespaces**. Refs in different groups render as independent,
separately-numbered lists, and **names are scoped per group**:

```wikitext
Regular citation.<ref>Source details</ref>
Explanatory note.<ref group="notes">Extra explanation</ref>

== Notes ==
<references group="notes" />   <!-- renders only the notes group -->

== References ==
<references />                  <!-- renders only the default group -->
```

Key facts:

- **Default group**: no `group=` attribute; rendered by plain
  `<references/>` or `{{reflist}}`.
- **Named groups** must have a matching `<references group="…"/>` or
  `{{reflist|group=…}}` to display; a group without its list is a Cite error
  ("group refs without references").
- **Names are scoped per group**: `name="apsis"` in group `"n"` is a
  different name from `name="apsis"` in the default group. Tooling must key
  named-ref resolution by `(group, name)`.
- **enwiki conventions**: `group="n"` / `group="note"` for a separate notes
  list; `group="lower-alpha"` via `{{efn}}` / `{{notelist}}` for explanatory
  footnotes (a, b, c…).
- Group identifiers must contain alphabetic characters; a purely numeric
  group name is a Cite error.
- To reuse a note: `<ref group="notes" name="example" />`.

---

## SOP: Structural Reference Errors (broken refs & duplicates)

Beyond template-level CS1 issues, refs themselves can be structurally broken.
These are the most common problems and their tracking categories — the
operational ground truth for citation maintenance on enwiki:

| Problem | Wikitext signature | Tracking category (enwiki) | Live size (2026-08) |
|---|---|---|---|
| Undefined named ref | `<ref name="X" />` with no definition of X | Pages with broken reference names | 6,271 |
| Duplicate name, different content | `<ref name="X">A</ref>…<ref name="X">B</ref>` | Pages with duplicate reference names | 38 |
| Malformed ref markup | unclosed `<ref>`, bad params | Pages with reference errors | 6,771 |
| Empty ref | `<ref></ref>` | Pages with incorrect ref formatting | 843 |
| Refs with no list | refs present, no `<references/>`/`{{reflist}}` | Pages with missing references list | 40 |

The Cite extension emits these as render-time errors; the tracking categories
above are populated automatically. **CheckWiki** additionally scans for these
as numbered checks (see
[the error list](https://en.wikipedia.org/wiki/Wikipedia:WikiProject_Check_Wikipedia/List_of_errors))
— notably **error 81 "Reference duplication"** (byte-identical `<ref>` tags
repeated instead of named-and-reused) with a ~165K-article backlog on enwiki.

### Detecting structural ref problems programmatically

Parse with mwparserfromhell and resolve names per `(group, name)`:

```python
import mwparserfromhell as mw

_refs = lambda parsed: parsed.filter_tags(
    matches=lambda t: str(t.tag).strip().lower() == "ref")

def group_name(tag):
    """Return (group, name) — names are scoped per group."""
    group = tag.get("group").value if tag.has("group") else ""
    name = tag.get("name").value if tag.has("name") else None
    return (str(group).strip().strip('"\''), str(name).strip().strip('"\'') if name else None)

def structural_problems(wikitext: str) -> list[str]:
    """Detect undefined reuses, duplicate names w/ different content, empties."""
    parsed = mw.parse(wikitext)
    tags = _refs(parsed)
    defs: dict[tuple, str] = {}      # (group, name) -> content
    dup_names: set = set()
    empty: int = 0
    for t in tags:
        _, name = group_name(t)
        content = str(t.contents).strip() if t.contents is not None else ""
        is_reuse = t.self_closing or not content
        if name and not is_reuse:
            if group_name(t) in defs and defs[group_name(t)] != content:
                dup_names.add(group_name(t))
            defs[group_name(t)] = content
        elif not is_reuse and not content:
            empty += 1
    problems = []
    for t in tags:
        _, name = group_name(t)
        content = str(t.contents).strip() if t.contents is not None else ""
        is_reuse = t.self_closing or not content
        if is_reuse and name and group_name(t) not in defs:
            problems.append(f"undefined named ref: {name!r}")
    for g, n in sorted(dup_names):
        problems.append(f"name defined with different content: {n!r}")
    if empty:
        problems.append(f"{empty} empty <ref></ref> tag(s)")
    return problems
```

> 💡 The sketch above is intentionally minimal. For production use, prefer a
> dedicated, tested implementation of this logic — e.g. a purpose-built
> duplicate-reference tool, or pywikibot's `DuplicateReferences` helper as a
> reference. Verify any tool's behavior on a sandbox page before running it
> at scale.

### Fixing duplicate references (CW81 pattern)

Name the first occurrence and reuse the rest:

```wikitext
<!-- before -->
First.<ref>Book ABC</ref> Again.<ref>Book ABC</ref>
<!-- after -->
First.<ref name="Unnamed-1">Book ABC</ref> Again.<ref name="Unnamed-1" />
```

Guardrails: merge only byte-identical, attribute-free refs; never touch refs
inside templates/comments/`<nowiki>`; skip pages with nested refs; avoid name
collisions (`Unnamed-N`, first free N); use a descriptive name (e.g. author +
year) instead of `Unnamed-N` when the source is identifiable — descriptive
names survive future edits better.

---

## SOP: Citoid Auto-Citation (REST API)

The [Citoid](https://www.mediawiki.org/wiki/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

```python
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()  # Returns array of citation objects

if citations:
    c = citations[0]  # Best match
    print(f"Type: {c.get('itemType')}")       # webpage, journalArticle, book, etc.
    print(f"Title: {c.get('title')}")
    print(f"Authors: {c.get('author', [])}")   # List of [last, first] pairs
    print(f"Date: {c.get('date')}")
    print(f"Publisher: {c.get('publisher')}")
    print(f"URL: {c.get('url')}")
```

### Fetch from a DOI

```python
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]
# Returns: itemType=journalArticle, title, volume, publicationTitle,
#          date, author, DOI, ISSN, url, accessDate
```

### Converting Citoid Data to a Citation Template

```python
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", "")

    # Map Citoid item types to CS1 templates
    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
    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}")

    # Type-specific fields
    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 field (for cite web when available)
    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)


# Usage:
citation = citoid_to_template(c)
print(citation)
```

### CLI with Citoid

```bash
# Fetch citation metadata for a URL and output as wiki template
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:

```python
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)
```

---

## Reference: Sub-referencing (WMDE, rolling out 2026)

Sub-referencing is the Wikimedia Deutschland technical wish that lets a source
be cited once with details, then **reused with different details** (e.g.
page numbers) without duplicating the full citation. It uses the `details`
attribute on the reuse tag:

```wikitext
<ref name="Smith2020">{{cite book |last=Smith |title=... |year=2020}}</ref>
Later page.<ref name="Smith2020" details="p. 42" />
Another page.<ref name="Smith2020" details="p. 87" />
```

- Rendered as a single base footnote plus per-use detail notes.
- **enwiki rollout is scheduled for 2026**; it is already live on some wikis
  (German Wikipedia was the first pilot).
- Tracking category: `Pages that use sub-references` (Cite extension).
- Relevance to maintenance: the "same source, different details" pattern is
  currently handled by duplicating citations or using `{{sfn}}`-style
  author-date; sub-referencing is the long-term replacement. When writing new
  citations or planning refactoring, prefer sub-references over duplicating a
  full citation with slightly different page numbers **once they are available
  on the target wiki**.

---

## 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

### ⚠️ Don't ignore `group=` when resolving named refs
Ref names are scoped per group. A reuse `<ref group="n" name="x" />` does
NOT resolve against `<ref name="x">…</ref>` in the default group. Tooling that
indexes named refs by bare name will produce false "undefined" and false
"has URL" results — always key by `(group, name)`.

### ⚠️ Don't write new refs with numeric or illegal names
Per WP:REFNAME, ref names must not contain `# < > [ ] { } |` and should not be
purely numeric (e.g. `:0`, `1`). Use descriptive names (`Smith-2015`);
auto-generated `Unnamed-N` is acceptable for bulk maintenance but descriptive
names survive future edits better.

### ⚠️ 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:
1. URLs from news sites that remove articles behind paywalls
2. Government pages that may disappear after administration changes
3. Academic preprints that may move to final published versions
4. 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

| Script | Purpose | Usage Example |
|--------|---------|---------------|
| [`scripts/expand-bare-url.sh`](./scripts/expand-bare-url.sh) | Expand a bare URL into a cite web template | `./expand-bare-url.sh https://example.com/article` |
| [`scripts/archive-check.sh`](./scripts/archive-check.sh) | Check if a URL is archived on the Wayback Machine | `./archive-check.sh https://example.com/article` |
| [`scripts/citoid-expand.sh`](./scripts/citoid-expand.sh) | Auto-generate a full citation template from URL/DOI/ISBN using Citoid | `./citoid-expand.sh 10.7554/eLife.32259` |
| [`scripts/check-dead-links.sh`](./scripts/check-dead-links.sh) | Extract URLs from a Wikipedia page and check each one | `./check-dead-links.sh Albert_Einstein` |
| [`scripts/citation-inspector.sh`](./scripts/citation-inspector.sh) | Show a summary of all citations on a page | `./citation-inspector.sh Albert_Einstein` |

### 🐍 Python Assets

| Script | Purpose | Usage Example |
|--------|---------|---------------|
| [`assets/wayback_inspector.py`](./assets/wayback_inspector.py) | Check Wayback Machine for URL archives, save new ones | `python3 wayback_inspector.py https://example.com` |
| [`assets/citoid_fetcher.py`](./assets/citoid_fetcher.py) | Auto-generate citations from URL/DOI/ISBN/PMID via the Citoid API | `python3 citoid_fetcher.py 10.7554/eLife.32259` |
| [`assets/dead_link_scanner.py`](./assets/dead_link_scanner.py) | Scan a Wikipedia page for dead links and suggest archives | `python3 dead_link_scanner.py Albert_Einstein` |
| [`assets/citation_linter.py`](./assets/citation_linter.py) | Parse and validate all citation templates in a page (AST-based; finds missing params, suspicious URLs, missing archives, identifier-only cites) | `python3 citation_linter.py Albert_Einstein` |
| [`assets/citation_generator.py`](./assets/citation_generator.py) | Interactive CLI to generate citation templates | `python3 citation_generator.py` |

### 📚 Reference Docs

| Document | Contents |
|----------|----------|
| [`references/cs1-parameters.md`](./references/cs1-parameters.md) | Complete parameter reference for all CS1/CS2 templates |
| [`references/maintenance-templates.md`](./references/maintenance-templates.md) | Full catalog of citation-related maintenance templates with usage guidance |
| [`references/tools-decision-guide.md`](./references/tools-decision-guide.md) | When to use each tool — flowchart, pipelines, strengths/weaknesses of all 10+ methods |
| [`references/fetch-contexts.md`](./references/fetch-contexts.md) | Where metadata fetching happens (your machine vs WMF servers vs Toolforge vs IA) and how it affects success rates |
| [`references/perennial-sources.md`](./references/perennial-sources.md) | Community consensus on 200+ frequently discussed sources — generally reliable, no consensus, generally unreliable, deprecated, and blacklisted, organized by category with cross-references to maintenance templates |

---

## Cross-References

| Related Skill | Why |
|--------------|-----|
| **[wikimedia-api-access](../wikimedia-api-access/SKILL.md)** | User-Agent and API patterns |
| **[wikimedia-wikitext](../wikimedia-wikitext/SKILL.md)** | AST parsing — required for reliable citation extraction (never regex) |
| **[wikipedia-page-anatomy](../wikipedia-page-anatomy/SKILL.md)** | Article structure — where citations appear |
| **[wikipedia-en-biography-writing](../wikipedia-en-biography-writing/SKILL.md)** | Biographies use citations extensively for BLP verification |
| **[wikipedia-reference-verifiability](../wikipedia-reference-verifiability/SKILL.md)** | Complementary URL detection — find bare URLs and missing URL parameters |
| **[wikipedia-categories](../wikipedia-categories/SKILL.md)** | Citation tracking categories for maintenance |
| **[wikipedia-error-handling](../wikipedia-error-handling/SKILL.md)** | API failures, rate limits (fetching citations/archives) |

