Skills for AI agents

Read-only tools that let an AI agent work with a newsroom's reporting the way a reporter would: search what has been published, open the sources behind it, read the public meeting transcripts it came from, and see what is coming up. Each one is an Agent Skills folder, so it runs in Claude Code, OpenClaw and any agent that reads the standard, or as a plain command-line program.

Everything here is read-only. A skill reads a newsroom's public API and never writes back.

Quick start

Three commands to a first result, no API key required (digest is a public mode):

npx clawhub install local-news-api
cd skills/local-news-api
python3 scripts/local_news_api.py digest

That prints the newsroom's recent stories as markdown. Everything past browsing needs your own key; the skill file below covers it.

Install

OpenClaw, or any agent, from ClawHub:

clawhub install local-news-api

Claude Code, into your personal skills folder:

npx clawhub install local-news-api --workdir ~/.claude

From source, kept current with git pull:

git clone https://github.com/news-community/news-skills.git ~/src/news-skills
ln -s ~/src/news-skills/skills/local-news-api ~/.claude/skills/local-news-api

No agent at all: once installed, run python3 scripts/local_news_api.py digest from the skill folder. It is a single Python file with no dependencies.

The instructions alone are at communities.news/skills/local-news-api, as raw Markdown for an agent to read. That file is instructions only: the skill also needs its scripts/ folder, so install it with one of the commands above rather than copying that file alone.

Newsrooms on the platform

NewsroomStatus
Alaska News (alaskanews.com)Live

The skills work with any newsroom on the platform. Alaska News is the first, so it is the default, and today it is the only one with reporting to read. Every result carries that newsroom's own usage terms, read from the newsroom each time rather than assumed. For Alaska News they currently say: no model training, and quote with attribution and a link back.

The skills

Local News API: Search Articles, Sources, Meeting Transcripts & Events

Local news API: articles on local government, city council, elections, courts, public safety, schools and health, their sources (meeting transcripts, people quoted, video clips) and a local events and public hearing calendar. Read-only. Communities News platform; live for Alaska News.

local-news-api · version 1.6.0 · ClawHub listing · raw SKILL.md · source

The full skill file: local-news-api

What an agent reads when it loads the skill, rendered here for people. The metadata block at the top of the file is left out.

Show the full skill file (19k characters)

Local News API

A read-only client for a local newsroom's public API on the Communities News platform. It pulls published articles, the sources behind them (meeting transcripts, people quoted, video clips) and upcoming meetings and deadlines into your own work, under the newsroom's terms.

The example throughout is Alaska News, the first newsroom on the platform and the default: site https://alaskanews.com, API https://alaskanews.com/api/v1, spec https://alaskanews.com/api/v1/openapi.json. Every newsroom on the platform serves the same API at its own domain.

This skill only consumes. It never submits, edits, or writes back to the platform. Submitting content into the newsroom is a separate, editor-authenticated workflow that is not part of this tool.


What it is, and what it is not

Audience community journalists, bloggers, civic writers; one key each
Direction READ ONLY. No PATCH / PUT / DELETE; its only POST is the read-only RAG query. A test enforces this.
Auth your own cn_ API key (COMMUNITIES_NEWS_API_KEY), ideally created read-only. digest, topics and tags need none.
Output rendered markdown by default (paste into your draft), --json for raw. Every response carries the site's usage terms.
Newsroom alaskanews.com (/api/v1) by default, a DEFAULT not a limit: NEWS_SITE + NEWS_COMMUNITY point it elsewhere.
Runs on scripts/local_news_api.py, Python 3, standard library only, no dependencies.

It is not a content generator. It produces source material that you turn into your own article, script, or post.


Terms (read this before you publish anything from it)

The terms are read from the newsroom on every run, not compiled in. The client fetches robots.txt's Content-Signal (the machine-readable location defined by contentsignals.org), falling back to llms.txt. alaskanews.com currently declares:

  • ai-train=no you may not train models on the content.
  • search=yes it may surface in AI-powered search.
  • ai-input=yes you may quote it with attribution and a backlink.

Fetched, not compiled in, so a newsroom's change of stance shows up at once and a different newsroom's reporting never carries Alaska's terms. If the terms cannot be read, the output says so and invents nothing.

Every mode prints that reminder under its output. It is not decoration: the person running this is republishing someone else's reporting, and the attribution + backlink is the consideration for using it. If you generate content from an article, name the newsroom (Alaska News, for alaskanews.com) and link the source.


Auth: your own key, and only what it reaches

Create a key in your account on the newsroom's site; for Alaska News, alaskanews.com/profile/settings. Tick "Read-only", and know its one cost first: rag will not work, because its read-only query is an HTTP POST.

The guarantee a read-only key gives is concrete: the server rejects every POST/PUT/PATCH/DELETE before a handler runs, so the key cannot change anything, whatever code or agent holds it. This client never writes either, but that is a promise in code you would have to read. A read-only key is still a credential: if it leaks, whoever has it can read what it reaches and spend its rate limit, so revoke a leaked one at the same settings page.

Then either export the key (works from anywhere):

export COMMUNITIES_NEWS_API_KEY=cn_...

or drop it in a gitignored .env.local next to the script (or in your project root, which may hold the key but never NEWS_SITE or PLATFORM_API_BASE):

echo 'COMMUNITIES_NEWS_API_KEY=cn_...' >> scripts/.env.local

Access is tiered on the platform side, and your key may not reach everything.

Mode Reach Note
digest public recent stories; --date for one day's, a summary each. Verified 2026-09-30
search any valid key six corpora; external_documents and social_post are editor/admin only
angles any valid key discovery scaffold; runs on the /search surface
brief any valid key research brief: one /search, arranged for a writer, then four blanks
article public by URL/slug, keyed by id the id form returns the richer record
transcript any valid key full meeting transcript with speakers + timestamps
events any valid key GET /calendar, date-ranged. Re-verified 2026-09-09
communities any valid key the slugs --community accepts
browse any valid key the published article list; --tag for one beat
people / person any valid key speaker directory, and one actor's coverage
topics / tags public beats ranked by coverage, and the subject vocabulary. Verified 2026-09-29
rag role-gated an external consumer key saw 403 on 2026-07-23; slow (~1-2 min) where allowed
clip id only resolves a known id to its public MP4 URL. You cannot BROWSE clips: see below

Some endpoints are not role gates, and no upgrade reaches them. A set of routes authenticate by cookie session only: they read the browser's Supabase session rather than the API-key path, so they refuse every cn_ key, including an admin's. GET /clips (browse) and GET /transcripts/search are the two you are most likely to want; GET /transcript/<id>/speakers is a third, which is why you can read every word of a meeting and not learn who said it.

You need not take that list from this file. GET /api/v1/me returns a reachability block derived from the platform's own router, and check renders it: session_auth_only (nothing to request) apart from requires_role (a membership you could be granted). Those endpoints return 403 with error: session_auth_only and a remedy, not a bare 401 that reads like a bad key.

Use search --corpus transcripts instead of transcripts/search, and get clip ids from search or an article.

How much weight these rows carry. They come from two passes with two different keys, and only one of them tells you about external reach. Read references/verification.md before changing any claim here about what a key reaches. Run check for the only answer that is about your key.

Run check first. It asks the server what your key reaches, and probes a handful of endpoints directly on top of that. It reports whether your key is read-only, your role per community, the endpoints no key reaches and the ones a membership would unlock. It takes about five seconds:

python3 scripts/local_news_api.py check

Modes

python3 scripts/local_news_api.py check                               # what does MY key reach? (run first)
python3 scripts/local_news_api.py digest                              # recent stories (no key)
python3 scripts/local_news_api.py digest --date today                 # one day's stories, a summary each (no key)
python3 scripts/local_news_api.py browse --sort new                   # what has been PUBLISHED (no query)
python3 scripts/local_news_api.py search "port of alaska settlement"  # --corpus, --since, --until
python3 scripts/local_news_api.py angles "port of alaska" --intent track  # discovery: fix 2 Ws, expand the rest
python3 scripts/local_news_api.py brief "port of alaska"             # research brief before writing (--out FILE)
python3 scripts/local_news_api.py article <id | slug | url>           # full article
python3 scripts/local_news_api.py transcript <source-id>              # meeting transcript
python3 scripts/local_news_api.py events                              # what is coming UP (next 30 days)
python3 scripts/local_news_api.py rag "public comment deadlines"      # answer + citations (not with a read-only key)
python3 scripts/local_news_api.py clip <clip-id>                      # resolve a known clip id to its MP4 URL
python3 scripts/local_news_api.py communities                         # slugs valid for --community
python3 scripts/local_news_api.py people "dunleavy"                   # the Who axis: named speakers
python3 scripts/local_news_api.py person <person-id>                  # one actor + the coverage they appear in
python3 scripts/local_news_api.py topics                              # the beats, ranked by coverage
python3 scripts/local_news_api.py tags "port" --category organization # the subject vocabulary

topics is the beats; tags is the whole vocabulary. topics lists the topic-category tags (Government, Infrastructure, Health...) ranked by articles published, each naming its parent; counts do not roll up into the parent. tags searches every tag: organization, topic or location. A slug from either is what browse --tag takes. Both are public.

browse vs search. search answers "what do you have about X". browse answers "what has been published", which is the question you ask before you know what X is. --sort takes new/hot/top/popular/timeline/alphabetical, and --tag <slug> lists a single beat.

A day's stories: digest --date. today, yesterday or YYYY-MM-DD: every story published that day, newest first, with time, place, one-line summary and link. It asks the public feed for exactly that day, so no key. The day is the newsroom's own, in the time zone the feed reports (Alaska News: America/Anchorage); --tz <Area/City> overrides it.

A research brief: brief "<topic>". The step before writing. One /search across everything your key reaches, in a writer's order (prior coverage, the meeting record, people on the record, events, beats), then four blanks to fill first: why it matters, whose voice is missing, what you will cite, and a premise check. Assembled, not generated: no model call, and the judgment is yours. --out brief.md also saves it, with the terms.

Pointing it at another newsroom

Every newsroom on the platform serves the same API at its own domain, so another newsroom is configuration, not a fork:

export NEWS_SITE=https://<host>          # the newsroom; its API is <host>/api/v1
export NEWS_COMMUNITY=<slug>             # default for --community
export COMMUNITIES_NEWS_API_KEY=cn_...   # (NEWS_DESK_API_KEY, ALASKA_DESK_API_KEY still work)

alaskanews.com remains the default, and today it is the only newsroom live on this platform, so that default is also the whole of production. Nothing about a market is compiled in: the terms, the See also links, the reachability report and every request path follow whatever NEWS_SITE and --community say. check prints which newsroom and community it is reporting on, because a reachability report that does not name its subject is the kind of thing you read wrongly once.

Paging. Every list mode takes --limit and --offset, and prints showing 1-20 of 340 with the next --offset when there is more. A page that quietly drops the rest is how you conclude there are three of something when there are ninety.

Add --json for raw responses, --community <slug> to target a community other than alaska-news (run communities to see which slugs exist).

The When axis: --since / --until. search and angles both take --since YYYY-MM-DD and --until YYYY-MM-DD, which map to the API's date_from / date_to on the articles corpus. This is the only one of the five Ws the server can filter on, and it is the axis the angles intents talk about holding or expanding, so --intent precedent --until 2020-01-01 is how you actually ask the question that intent describes. Result lines carry the date for the same reason: you cannot check the two-axis relevance rule below against results whose dates are hidden.

events is forward-looking. It reads GET /calendar, whose window starts now and runs 30 days (--days to change it, --type meeting|public_notice|community_event|class to narrow), so it never lists a past meeting as upcoming. To search events by relevance across all time, including past ones, use search "<q>" --corpus events. /calendar has no full-text parameter, so a query argument to events filters the window client-side on title and location.


Every output points onward

Every mode ends with prioritized Next steps (each with its why), the related modes and a see-also, and errors carry a recovery rather than a bare status. --json is the machine surface and stays valid JSON. How that works, including the API's own next_steps: references/output.md.


Working the story: the five Ws (the angles mode)

Treat Who, What, When, Where, Why as five independent axes. The investigative method has two modes, and they land on opposite sides of this tool's read-only line:

  • Matching (ranking which stories are genuinely related) is axis-weighted similarity. That is the server's job; it happens inside /search and /rag/query. This client does not re-do it.
  • Discovery (fix two Ws, expand the rest) is a workflow of chained searches, and that is exactly what a sourcing tool can scaffold. It is what angles runs.

angles --intent names which two Ws you fix:

Intent (the two Ws it fixes) --intent Runs now: one /search over Suggested next
similar stories (What + Why) similar articles rag to hold What+Why and vary When
historical precedent (What + Why) precedent articles, transcripts --until an earlier year
track a company or agency (Who + What) track articles, transcripts the actor across more years
local coverage (Where + What) local articles, events, transcripts --community <slug> is the Where
same narrative angle (Why + What) angle articles rag on who is using the framing

Each run is one /search; everything in the last column is a Next step you choose to run, so you drive the depth, not the key's rate limit.

The one rule to carry: two matching dimensions (the topic and the actor, say) tell you a piece is relevant; they do not make a claim corroborated. Several articles can repeat one underlying source. Verify a claim against an independent source, ideally the primary record, before you treat it as confirmed, and cite a specific piece, never "previously reported".


Method: internal before external

Before you reach for a general web search, ask whether the newsroom (Alaska News, by default) has already covered this. Run search first. If the answer is in the corpus, you save an external lookup and you can cite specific prior reporting; if it isn't, you now know it's a genuine gap. The reorder is the value.

When you cite "previously reported," cite a specific article (its id or URL), never a generic phrase with no matching published story behind it.


Honest limits (as of 2026-09-09)

  • What was verified, with which key: references/verification.md. check is the only answer about your key.
  • rag is role-gated and slow. An external key saw 403. Where it is allowed, it synthesizes an answer over retrieved passages and measured 84 seconds on 2026-09-09, so the client gives it a 180s budget, and a timeout is reported as one. Quote its citations (each carries a verbatim excerpt and a url), never its synthesized paragraph: the paragraph is a summary of someone else's reporting and is not itself attributable.
  • You cannot browse clips or use /transcripts/search with any key. Both are cookie-session endpoints, not role gates. See the auth section above.
  • angles and brief each make one /search call, so any key that reaches search reaches them. angles' suggested rag follow-ups inherit rag's role limit.
  • Rate limits. 300 reads/min per user (and 180 writes/min, which nothing here uses). A 429 is a back-off, not a permission problem.
  • This is not a submission tool. If you want your work published on the newsroom, that is a newsroom workflow, not this.

Not covered here, deliberately

person reports "appears in", not "quoted in", and the distinction is load-bearing. /persons/<id>/articles returns a UNION: articles where an attribution matched the person's name, and articles structurally linked to them. Only the first kind carries a verbatim excerpt. Rows with a quote are marked quoted (Nx); when a whole page has none, the client says so, because "Dunleavy appears in this piece" and "Dunleavy said this in this piece" are different claims and only one of them is quotable. Open the article and confirm before you attribute words to anyone.

Everything else on the read surface that a consumer key reaches is now wrapped. What remains unwrapped is write-side or editor-only, and out of scope by design.