openfec
OpenFEC — campaign finance data, candidates, committees, filings, and contribution search.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-openfec
Repository
Skill path: skills/aiwithabidi/openfec
OpenFEC — campaign finance data, candidates, committees, filings, and contribution search.
Open repositoryBest for
Primary workflow: Analyze Data & AI.
Technical facets: Full Stack, Data / AI.
Target audience: everyone.
License: MIT.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install openfec into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding openfec to shared team environments
- Use openfec for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: openfec
description: "OpenFEC — campaign finance data, candidates, committees, filings, and contribution search."
homepage: https://www.agxntsix.ai
license: MIT
compatibility: Python 3.10+ (stdlib only — no dependencies)
metadata: {"openclaw": {"emoji": "🗳️", "requires": {"env": ["FEC_API_KEY"]}, "primaryEnv": "FEC_API_KEY", "homepage": "https://www.agxntsix.ai"}}
---
# 🗳️ OpenFEC
OpenFEC — campaign finance data, candidates, committees, filings, and contribution search.
## Requirements
| Variable | Required | Description |
|----------|----------|-------------|
| `FEC_API_KEY` | ✅ | FEC API key (or DEMO_KEY) |
## Quick Start
```bash
# Search candidates
python3 {{baseDir}}/scripts/openfec.py search-candidates --q <value> --office <value> --state <value> --party <value> --cycle <value> --per-page "20"
# Get candidate details
python3 {{baseDir}}/scripts/openfec.py get-candidate <id>
# Get candidate financial totals
python3 {{baseDir}}/scripts/openfec.py candidate-totals <id> --cycle <value>
# Search committees
python3 {{baseDir}}/scripts/openfec.py search-committees --q <value> --committee-type <value> --per-page "20"
# Get committee details
python3 {{baseDir}}/scripts/openfec.py get-committee <id>
# List filings
python3 {{baseDir}}/scripts/openfec.py list-filings --candidate-id <value> --committee-id <value> --per-page "20"
# Search individual contributions
python3 {{baseDir}}/scripts/openfec.py search-contributions --contributor-name <value> --contributor-state <value> --min-amount <value> --max-amount <value> --per-page "20"
# Search disbursements
python3 {{baseDir}}/scripts/openfec.py search-disbursements --committee-id <value> --recipient-name <value> --per-page "20"
# Get election results
python3 {{baseDir}}/scripts/openfec.py election-results --office "president" --cycle <value>
# Totals by entity type
python3 {{baseDir}}/scripts/openfec.py get-totals --cycle <value>
```
## Output Format
All commands output JSON by default.
## Script Reference
| Script | Description |
|--------|-------------|
| `{baseDir}/scripts/openfec.py` | Main CLI — all commands in one tool |
## Credits
Built by [M. Abidi](https://www.linkedin.com/in/mohammad-ali-abidi) | [agxntsix.ai](https://www.agxntsix.ai)
[YouTube](https://youtube.com/@aiwithabidi) | [GitHub](https://github.com/aiwithabidi)
Part of the **AgxntSix Skill Suite** for OpenClaw agents.
📅 **Need help setting up OpenClaw for your business?** [Book a free consultation](https://cal.com/agxntsix/abidi-openclaw)
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "aiwithabidi",
"slug": "openfec",
"displayName": "Openfec",
"latest": {
"version": "1.0.0",
"publishedAt": 1772751181787,
"commit": "https://github.com/openclaw/skills/commit/4972847cfa2fc1e583cc17880d20f5e1990b3ec6"
},
"history": []
}
```
### scripts/openfec.py
```python
#!/usr/bin/env python3
"""OpenFEC CLI — OpenFEC — campaign finance data, candidates, committees, filings, and contribution search.
Zero dependencies beyond Python stdlib.
"""
import argparse
import json
import os
import sys
import urllib.request
import urllib.error
import urllib.parse
API_BASE = "https://api.open.fec.gov/v1"
def get_env(name):
val = os.environ.get(name, "")
if not val:
env_path = os.path.join(os.environ.get("WORKSPACE", os.path.expanduser("~/.openclaw/workspace")), ".env")
if os.path.exists(env_path):
with open(env_path) as f:
for line in f:
line = line.strip()
if line.startswith(name + "="):
val = line.split("=", 1)[1].strip().strip('"').strip("'")
break
return val
def req(method, url, data=None, headers=None, timeout=30):
body = json.dumps(data).encode() if data else None
r = urllib.request.Request(url, data=body, method=method)
r.add_header("Content-Type", "application/json")
if headers:
for k, v in headers.items():
r.add_header(k, v)
try:
resp = urllib.request.urlopen(r, timeout=timeout)
raw = resp.read().decode()
return json.loads(raw) if raw.strip() else {}
except urllib.error.HTTPError as e:
err = e.read().decode()
print(json.dumps({"error": True, "code": e.code, "message": err}), file=sys.stderr)
sys.exit(1)
def api(method, path, data=None, params=None):
"""Make authenticated API request."""
base = API_BASE
api_key = get_env("FEC_API_KEY") or "DEMO_KEY"
headers = {}
if params is None:
params = {}
params["api_key"] = api_key
url = f"{base}{path}"
if params:
qs = urllib.parse.urlencode({k: v for k, v in params.items() if v}, doseq=True)
url = f"{url}{'&' if '?' in url else '?'}{qs}"
return req(method, url, data=data, headers=headers)
def out(data):
print(json.dumps(data, indent=2, default=str))
def cmd_search_candidates(args):
"""Search candidates"""
path = "/candidates/search/?api_key={api_key}"
params = {}
if args.q:
params["q"] = args.q
if args.office:
params["office"] = args.office
if args.state:
params["state"] = args.state
if args.party:
params["party"] = args.party
if args.cycle:
params["cycle"] = args.cycle
if args.per_page:
params["per-page"] = args.per_page
result = api("GET", path, params=params)
out(result)
def cmd_get_candidate(args):
"""Get candidate details"""
path = "/candidate/{id}/?api_key={api_key}"
path = path.replace("{id}", str(args.id))
result = api("GET", path)
out(result)
def cmd_candidate_totals(args):
"""Get candidate financial totals"""
path = "/candidate/{id}/totals/?api_key={api_key}"
path = path.replace("{id}", str(args.id))
params = {}
if args.cycle:
params["cycle"] = args.cycle
result = api("GET", path, params=params)
out(result)
def cmd_search_committees(args):
"""Search committees"""
path = "/committees/?api_key={api_key}"
params = {}
if args.q:
params["q"] = args.q
if args.committee_type:
params["committee-type"] = args.committee_type
if args.per_page:
params["per-page"] = args.per_page
result = api("GET", path, params=params)
out(result)
def cmd_get_committee(args):
"""Get committee details"""
path = "/committee/{id}/?api_key={api_key}"
path = path.replace("{id}", str(args.id))
result = api("GET", path)
out(result)
def cmd_list_filings(args):
"""List filings"""
path = "/filings/?api_key={api_key}"
params = {}
if args.candidate_id:
params["candidate-id"] = args.candidate_id
if args.committee_id:
params["committee-id"] = args.committee_id
if args.per_page:
params["per-page"] = args.per_page
result = api("GET", path, params=params)
out(result)
def cmd_search_contributions(args):
"""Search individual contributions"""
path = "/schedules/schedule_a/?api_key={api_key}"
params = {}
if args.contributor_name:
params["contributor-name"] = args.contributor_name
if args.contributor_state:
params["contributor-state"] = args.contributor_state
if args.min_amount:
params["min-amount"] = args.min_amount
if args.max_amount:
params["max-amount"] = args.max_amount
if args.per_page:
params["per-page"] = args.per_page
result = api("GET", path, params=params)
out(result)
def cmd_search_disbursements(args):
"""Search disbursements"""
path = "/schedules/schedule_b/?api_key={api_key}"
params = {}
if args.committee_id:
params["committee-id"] = args.committee_id
if args.recipient_name:
params["recipient-name"] = args.recipient_name
if args.per_page:
params["per-page"] = args.per_page
result = api("GET", path, params=params)
out(result)
def cmd_election_results(args):
"""Get election results"""
path = "/elections/?api_key={api_key}"
params = {}
if args.office:
params["office"] = args.office
if args.cycle:
params["cycle"] = args.cycle
result = api("GET", path, params=params)
out(result)
def cmd_get_totals(args):
"""Totals by entity type"""
path = "/totals/by_entity/?api_key={api_key}"
params = {}
if args.cycle:
params["cycle"] = args.cycle
result = api("GET", path, params=params)
out(result)
def main():
parser = argparse.ArgumentParser(description="OpenFEC CLI")
sub = parser.add_subparsers(dest="command")
sub.required = True
p_search_candidates = sub.add_parser("search-candidates", help="Search candidates")
p_search_candidates.add_argument("--q", required=True)
p_search_candidates.add_argument("--office", required=True)
p_search_candidates.add_argument("--state", required=True)
p_search_candidates.add_argument("--party", required=True)
p_search_candidates.add_argument("--cycle", required=True)
p_search_candidates.add_argument("--per-page", default="20")
p_search_candidates.set_defaults(func=cmd_search_candidates)
p_get_candidate = sub.add_parser("get-candidate", help="Get candidate details")
p_get_candidate.add_argument("id")
p_get_candidate.set_defaults(func=cmd_get_candidate)
p_candidate_totals = sub.add_parser("candidate-totals", help="Get candidate financial totals")
p_candidate_totals.add_argument("id")
p_candidate_totals.add_argument("--cycle", required=True)
p_candidate_totals.set_defaults(func=cmd_candidate_totals)
p_search_committees = sub.add_parser("search-committees", help="Search committees")
p_search_committees.add_argument("--q", required=True)
p_search_committees.add_argument("--committee-type", required=True)
p_search_committees.add_argument("--per-page", default="20")
p_search_committees.set_defaults(func=cmd_search_committees)
p_get_committee = sub.add_parser("get-committee", help="Get committee details")
p_get_committee.add_argument("id")
p_get_committee.set_defaults(func=cmd_get_committee)
p_list_filings = sub.add_parser("list-filings", help="List filings")
p_list_filings.add_argument("--candidate-id", required=True)
p_list_filings.add_argument("--committee-id", required=True)
p_list_filings.add_argument("--per-page", default="20")
p_list_filings.set_defaults(func=cmd_list_filings)
p_search_contributions = sub.add_parser("search-contributions", help="Search individual contributions")
p_search_contributions.add_argument("--contributor-name", required=True)
p_search_contributions.add_argument("--contributor-state", required=True)
p_search_contributions.add_argument("--min-amount", required=True)
p_search_contributions.add_argument("--max-amount", required=True)
p_search_contributions.add_argument("--per-page", default="20")
p_search_contributions.set_defaults(func=cmd_search_contributions)
p_search_disbursements = sub.add_parser("search-disbursements", help="Search disbursements")
p_search_disbursements.add_argument("--committee-id", required=True)
p_search_disbursements.add_argument("--recipient-name", required=True)
p_search_disbursements.add_argument("--per-page", default="20")
p_search_disbursements.set_defaults(func=cmd_search_disbursements)
p_election_results = sub.add_parser("election-results", help="Get election results")
p_election_results.add_argument("--office", default="president")
p_election_results.add_argument("--cycle", required=True)
p_election_results.set_defaults(func=cmd_election_results)
p_get_totals = sub.add_parser("get-totals", help="Totals by entity type")
p_get_totals.add_argument("--cycle", required=True)
p_get_totals.set_defaults(func=cmd_get_totals)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
```