# Candidate Schema

> Generate structured data (JSON-LD Person schema) for a political candidate's own website so AI search engines and answer engines correctly identify and disambiguate them from people with similar names. Use this skill whenever a candidate or their campaign staff wants to add schema markup, structured data, JSON-LD, a "knowledge panel" signal, Wikidata/sameAs links, or "help AI find me / tell me apart from the other [name]" to their site. Trigger it for phrases like "candidate schema," "structured data for my campaign site," "AI can't tell me apart from," "add JSON-LD," "disambiguate me," or when someone pastes their campaign facts and asks how to make search/AI recognize them. Also produces platform-specific paste-in instructions for Wix, Squarespace, WordPress, and RUN! (designedtorun.com).

- Skill: `meturley/candidate-schema` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add meturley/candidate-schema`
- Raw SKILL.md: https://api.skillmd.com/api/skills/meturley/candidate-schema/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: meturley (https://skillmd.com/u/meturley)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/meturley/candidate-schema

---


# Candidate Schema

Generate a complete, syntactically valid `<script type="application/ld+json">` block describing a political candidate as a schema.org `Person`, plus plain-English instructions for pasting it into their website. The goal is disambiguation: helping AI search engines and answer engines correctly identify the candidate and distinguish them from other people who share their name.

This skill is part of a suite that shares a `fact-sheet.md` file (see "The fact-sheet.md convention" below). Read from it first; write new verified facts back to it.

## Core workflow

1. **Look for `fact-sheet.md`** in the project/working directory. If it exists, read every field from it instead of re-asking. Only ask the user for fields that are missing or blank.
2. **Interview** the user for any missing verified facts (see "Fields to collect").
3. **Write** any newly gathered facts back to `fact-sheet.md` (create it if absent), so sibling skills can reuse them.
4. **Generate** the JSON-LD block.
5. **Validate** that the emitted JSON is syntactically correct (see "Validation").
6. **Output** the block plus platform-specific paste-in instructions and the FEC disclaimer note.

## Hard rules — never violate these

These protect the user legally and protect the integrity of the output. State them plainly if the user pushes against them.

- **Only describe the candidate running this skill, on properties they own.** Generate schema for one real person — the user (or the candidate the user represents) — intended for that person's own website. Never generate schema describing an opponent, a third party, or for placement on a site the user doesn't control. If asked to, decline and explain that structured data must live on the subject's own owned property to be trusted, and that misrepresenting authorship of another person's identity markup is deceptive.
- **Never fabricate a Wikidata ID, social URL, Ballotpedia URL, or any other identifier.** If the user doesn't have one, omit that `sameAs` entry entirely. A wrong or invented ID actively harms disambiguation — it's worse than omitting it. Never guess a Q-ID from the name. Only include URLs the user explicitly provides or confirms.
- **Only include facts the user has verified.** Don't infer a district, party, or job title. If a field is missing, ask; if the user can't confirm, leave it out rather than guessing.
- **Always validate the JSON** before presenting it (see "Validation"). Malformed JSON-LD is silently ignored by crawlers, so an unvalidated block is worthless.
- **Always include the FEC disclaimer note** (see "FEC disclaimer note") in the output.

## Fields to collect

Ask for these in a single friendly batch (skip any already in `fact-sheet.md`). Explain that everything must be a verified fact, since it's identity markup.

- **Full name** — the candidate's full legal/campaign name as it should appear.
- **Office sought** — e.g. "U.S. House of Representatives," "Ohio State Senate," "Mayor of Columbus."
- **Current / most recent office** — if any; otherwise note they're not a current officeholder.
- **District or jurisdiction** — e.g. "Ohio's 9th congressional district," "Ward 3," "statewide."
- **Party** — e.g. Democratic, Republican, Independent, Green.
- **Key policy areas** — the topics they campaign on (becomes `knowsAbout`), e.g. healthcare, veterans' affairs, infrastructure.
- **Official website** — the campaign site URL (the site this markup will live on).
- **Social profile URLs** — every profile they want linked: X/Twitter, Facebook, Instagram, YouTube, TikTok, LinkedIn, Bluesky, Threads, etc. Collect the full URLs.
- **Ballotpedia URL** — if they have a Ballotpedia page.
- **Wikidata Q-ID** — if they have one (format `Q` followed by digits, e.g. `Q12345`). If they're not sure, they can search wikidata.org for their name; if none exists, omit it.
- **Key accomplishments** — optional, each with a date and a source, used only for the `fact-sheet.md` and any optional FAQ answers.

When you have what you need (or the user says they don't have some items), proceed. Missing optional items are fine — just omit them from the output.

## Building the JSON-LD

Read `references/schema-spec.md` for the exact property mapping, the `disambiguatingDescription` formula, the optional nested `ProfilePage` and `FAQPage` structures, and worked examples. Follow that spec precisely.

Summary of what the `Person` object must contain:
- `name` — full name.
- `sameAs` — an array of **every** provided social URL plus the Wikidata entity URL (`https://www.wikidata.org/wiki/Q…`) and Ballotpedia URL, if provided. Omit any the user lacks. Never invent entries.
- `disambiguatingDescription` — a single one-line identity statement (see the formula in the spec), e.g. "U.S. Representative for Ohio's 9th congressional district, elected 2024."
- `knowsAbout` — array of the policy areas.
- `jobTitle` — current or sought office title.
- Optionally a nested `mainEntityOfPage` → `ProfilePage`, and/or a `FAQPage` block, where the candidate has content that warrants them (see spec).

## Validation

Before showing the block to the user, validate it. Extract the JSON from inside the `<script>` tags and parse it:

```bash
python3 scripts/validate_jsonld.py <path-to-file-containing-the-block>
```

The script strips the `<script type="application/ld+json">` wrapper, runs `json.loads`, and checks that required Person keys are present and that `sameAs` is an array with no obviously malformed URLs. If it reports an error, fix the block and re-run until it passes. Only present validated output. Do not claim the JSON is valid unless the validator confirmed it.

## Output format

Present the result in three clearly separated parts:

1. **The block** — the full `<script type="application/ld+json">…</script>`, in a copy-pasteable code block, with a one-line note that it's been validated.
2. **Where to paste it** — the platform-specific instructions. Read `references/platform-instructions.md` and include the sections for the platform(s) the user uses (or all four if unknown): Wix, Squarespace, WordPress, and RUN! (designedtorun.com). Each names the exact settings field the block goes into.
3. **FEC disclaimer note** — the templated reminder (see below).

## FEC disclaimer note

Always append this note (fill the bracket with the committee name if known, otherwise leave the template placeholder). The regulator reference depends on the office level from "Office sought" — the FEC only has jurisdiction over federal races (U.S. House, U.S. Senate, President); state and local offices (state legislature, mayor, city council, county positions, etc.) are governed by their own state or local election/campaign-finance authority, not the FEC.

**For federal offices:**

> **Reminder — "Paid for by" disclaimer:** Federal campaign sites generally must carry a disclaimer identifying who paid for the communication. This JSON-LD is invisible identity markup and does **not** satisfy that requirement. Make sure your site displays a visible line such as *"Paid for by [Committee Name]"* (e.g., in the footer). Requirements vary by jurisdiction — confirm the exact wording with your compliance counsel or the FEC. This note is a reminder, not legal advice.

**For state or local offices:**

> **Reminder — "Paid for by" disclaimer:** Campaign sites generally must carry a disclaimer identifying who paid for the communication. This JSON-LD is invisible identity markup and does **not** satisfy that requirement. Make sure your site displays a visible line such as *"Paid for by [Committee Name]"* (e.g., in the footer). Requirements vary by state and locality — confirm the exact wording with your compliance counsel or your state/local election authority (the FEC has no jurisdiction over state or local races). This note is a reminder, not legal advice.

If the office level is ambiguous or unclear from what the user provided, ask before appending the disclaimer, rather than guessing which regulator to name.

## The fact-sheet.md convention

All skills in this suite read from and write to a shared `fact-sheet.md` in the project directory. It's a plain markdown file with labeled fields. When this skill gathers new verified facts, it appends or updates them here so sibling skills don't re-ask. Use `assets/fact-sheet-template.md` as the canonical structure. The fields are:

- **Name**
- **Office Sought**
- **Current Office**
- **District**
- **Party**
- **Policy Areas**
- **Website**
- **Socials** (one labeled URL per line)
- **Wikidata ID**
- **Ballotpedia**
- **Key Accomplishments** (each a bullet with a date and a source URL)

When writing back: preserve existing values, fill blanks, and never overwrite a user-verified value with a guess. If a value changes, confirm with the user first.

## Runtime & scope

- **Posture:** Generative with a validation gate. It produces JSON-LD identity markup for the candidate's own site and validates the block before presenting it.
- **Neutrality:** Only describes the candidate running the skill, on properties they own; never fabricates an identifier (Wikidata Q-ID, social or Ballotpedia URL) and never marks up an opponent or third party.
- **Runtime:** Python 3.6+, standard library only (the `validate_jsonld.py` script). No third-party dependencies.

