For AI agents

Let your AI agent build the newsletter.

Claude, Codex, Grok, Meta Muse or any AI assistant can work inside lightbreak: fill an issue, write each section in your voice, fact-check it against its sources and hand a finished draft to Beehiiv or ActiveCampaign. A person still decides when it sends.

Actions
47
Every step of an issue
Connects over
MCP or HTTPS
Works with any agent
Access
A key per agent
One workspace, revocable
Notifications
5 events
Signed webhooks
Sends email
Never
Drafts go to your email platform
What it does

What an agent can do

Most newsletter work repeats every week: gather the stories and events, write them in the publication's voice, check the facts, and get a clean draft into the email platform. lightbreak already does each step for people. An AI agent can do them too, with the same rules, the same section instructions and the same fact-checking review.

You say

"Build Thursday's Leander Scoop, run the review, and push the draft to Beehiiv."

The agent runs

generate_issue review_issue push_to_beehiiv

Connect

Connect your agent in three steps

  1. Make a keyIn lightbreak, open Settings, then AI agents & API, then New key. Name it after the agent and copy it right away: it's shown once.
  2. Point your agent at lightbreakGive it this address and header, with your key in place of the placeholder.
    URL:    https://app.lightbreak.ai/api/mcp
    Header: Authorization: Bearer lbk_YOUR_KEY
  3. Say helloAsk it: "List my lightbreak newsletters." If it answers, you're connected.
Claudeclaude.ai or the desktop app, one click

In Claude, open Settings, then Connectors, then browse. Add https://app.lightbreak.ai/api/mcp as a custom connector and press Connect. Claude asks you to sign in to lightbreak and approve it, and your access is granted. No key to copy. (In the Claude Code terminal, use the command below instead.)

Claude Codeterminal, Claude desktop app, Conductor

Run once, with your key in place of the placeholder:

claude mcp add --scope user --transport http lightbreak https://app.lightbreak.ai/api/mcp --header "Authorization: Bearer lbk_YOUR_KEY"
CodexOpenAI

Add to ~/.codex/config.toml:

[mcp_servers.lightbreak]
url = "https://app.lightbreak.ai/api/mcp"
http_headers = { Authorization = "Bearer lbk_YOUR_KEY" }
OpenCodeopencode.json

Keep the key in its own private file and point to it:

"mcp": {
  "lightbreak": {
    "type": "remote",
    "url": "https://app.lightbreak.ai/api/mcp",
    "headers": { "Authorization": "Bearer {file:~/.secrets/lightbreak.key}" }
  }
}
Cursor, Windsurf and other MCP appsremote MCP server

Add a remote (HTTP) MCP server with the address https://app.lightbreak.ai/api/mcp and the header Authorization: Bearer followed by your key.

Grok, Meta Muse, ChatGPT and custom agentsMCP, actions or plain HTTPS

If it asks for an MCP server, use the address and header above. If it asks for an OpenAPI or "actions" link, use https://app.lightbreak.ai/api/agent/openapi with the same key. Anything that can send a web request can call an action directly with POST https://app.lightbreak.ai/api/agent/<action>.

Every action

All 47 actions

Newsletters and issues

See every newsletter, read its selection rules, inspect a saved template's sections, start or generate an issue, compare expected and present sections, and delete an issue.

  • list_publications
  • list_issues
  • list_templates
  • get_template
  • get_selection_rules
  • create_issue
  • generate_issue
  • get_generation_status
  • get_issue
  • get_issue_tree
  • delete_issue

Finding content

Browse the stories, events, jobs and places lightbreak already collected, read any link, manage sources.

  • browse_content
  • browse_places
  • browse_listings
  • browse_jobs
  • read_link
  • list_sources
  • add_source

Writing and editing

Have lightbreak write in each section's voice, hide or show a section on this issue without changing its template, add stories, events and jobs, edit or remove content.

  • set_section_visibility
  • write_story_for_section
  • add_story
  • add_event
  • add_job
  • update_event
  • update_job
  • add_featured_home
  • set_featured
  • list_meme_sets
  • add_meme_set
  • update_block_text
  • update_issue_details
  • remove_blocks
  • save_issue_tree
  • upload_image_from_url
  • make_gif
  • suggest_subject_lines
  • write_intro

Checking and finishing

Fact-check every link, image, claim and sentence, then export or push a draft to the ESP.

  • review_issue
  • get_review
  • export_issue_html
  • push_to_beehiiv
  • push_to_activecampaign
  • mark_issue_done

Notifications

Get told the moment a draft is ready, a review finishes, or a draft reaches the ESP.

  • add_notification
  • list_notifications
  • test_notification
  • remove_notification
A week with an agent

How a week's issue can run

  1. 01

    Build. The agent pulls this week's stories, events and restaurants that lightbreak already collected, or brings its own.

  2. 02

    Write. lightbreak writes each piece with that section's own instructions and voice, exactly as when a person drags a story in.

  3. 03

    Review. The reviewer opens every link and source and checks each claim. The agent fixes what it flags and runs it again.

  4. 04

    Draft. The finished issue goes to Beehiiv or ActiveCampaign as a draft, and your notification tells your team it's ready.

What a key can and can't do

Can

Everything a newsletter manager does day to day, including deleting issues and pushing drafts, inside the one workspace the key belongs to (or only the newsletters you pick).

Can't

Manage people or other keys, open lightbreak's screens, reach another workspace, or send email.

Make a key
Questions

Questions people ask

Want to see an agent build a real issue first? Book a demo and we'll run one with your sources.

Is it safe to give an AI a key?
A key only works inside one workspace, can't manage people or other keys, and can be switched off in one click. lightbreak keeps a fingerprint of the key, never the key itself.
Can an agent send my newsletter?
No. lightbreak doesn't send email. The furthest an agent can go is putting a draft into Beehiiv or ActiveCampaign, where a person schedules it.
Will it overwrite what my team is editing?
No. If someone saved the issue in the meantime, the agent's save is refused with a clear conflict instead of replacing their work.
Does the AI make things up?
That's what the review is for. It opens every story's source, checks each claim, and only calls something wrong when it can quote the source that proves it.
What does it cost?
Connecting is free. Writing, generating and reviewing use AI the same way they do when a person clicks the button. A full review is a few cents per issue.
Reference, generated from the live API

For AI agents

This part is written for software. If you are an AI agent helping a person use lightbreak, everything you need is here, generated from the same catalog the API serves. The same reference as plain text: lightbreak.ai/agents.md.

AConnection

MCP endpoint
https://app.lightbreak.ai/api/mcp
Transport
Streamable HTTP, JSON responses. Protocol 2025-06-18 (also 2024-11-05, 2025-03-26)
Authentication
Authorization: Bearer lbk_... (workspace API key)
Plain HTTPS
POST https://app.lightbreak.ai/api/agent/<action> with the arguments as a JSON body
OpenAPI
https://app.lightbreak.ai/api/agent/openapi (send the key)
Scope
One workspace, newsletter-manager level, optionally limited to chosen publications
Errors
HTTP status with {"error": "plain-English sentence"}; over MCP the result has isError: true
Saving
save_issue_tree needs expected_updated_at; a newer save returns 409 conflict instead of overwriting
Email
Never sent. push_to_beehiiv and push_to_activecampaign create drafts only
Keys
Created by a workspace admin at Settings > AI agents & API; shown once; revocable

BRecommended workflow

  1. list_publications, then list_issues for one publication.
  2. Either generate_issue (lightbreak's own AI writes the whole issue; poll get_generation_status) or create_issue and fill it yourself.
  3. get_selection_rules to read what belongs in this publication, get_template to inspect the saved layout, then get_issue to compare expected and present sections.
  4. Hide a section that cannot be made good with set_section_visibility and a clear reason. This affects only this issue, is reversible, and prevents the section from appearing in previews or exports.
  5. Edit: update_block_text for a single text block's words (update_event / update_job for events and jobs), update_issue_details for subject and preview text, or get_issue_tree + save_issue_tree for structural changes (always pass back the updatedAt you read).
  6. review_issue, then poll get_review. The reviewer checks every visible link, image, fact (against each story's source) and sentence. Hidden sections are reported as skipped. Findings marked "wrong" include the source's own words. Fix what it finds and review again.
  7. export_issue_html, or push_to_beehiiv / push_to_activecampaign when the issue is ready. mark_issue_done marks it finished in lightbreak.
  8. Instead of polling, add_notification to be told when a draft is generated, a review finishes, or a draft reaches the ESP.

CActions (47)

ActionWhat it doesInputs
Newsletters and issues
list_publicationsList the newsletters (publications) this key can work on: id, name, slug, locale and timezone.
none
list_issuesList recent issues, newest first. Optionally only one publication.
publication_id: string (optional)
limit: integer (optional)
list_templatesSaved issue templates for a publication (or the whole workspace with scope=workspace). Use get_template for ordered section details.
publication_id: string (optional)
scope: publication | workspace (optional)
get_templateRead one saved template's ordered sections: key, name, type, and writing instructions.
template_id: string
get_selection_rulesRead the resolved content-selection rules for one publication: hard bans, city scope, home band, banned sources, quality priorities and exact section count ranges. This is the same data Full Generate and daily generation enforce.
publication_id: string
create_issueCreate an empty issue for a date from the publication's template. Returns the issue id (or the existing one for that date).
publication_id: string
date: string
generate_issueHave lightbreak's own AI write a whole issue for a date using the publication's sources and section instructions. Runs in the background; poll get_generation_status with the run id.
publication_id: string
date: string (optional)
get_generation_statusCheck a generate_issue run. When finished it includes the issue id.
publication_id: string
run_id: string
get_issueRead an issue outline including each section's visibility and template origin, plus templateSections showing expected sections as present, hidden, or missing.
issue_id: string
get_issue_treeRead the full raw block tree. Each section includes hidden, hiddenReason, and templateSectionKey; save_issue_tree preserves these issue-local fields.
issue_id: string
delete_issueDelete an issue.
issue_id: string
Finding content
browse_contentStories, events and other items lightbreak has already collected from a publication's sources, newest first. kind: news, events, pets, jobs, tools, youtube, business, restaurants. Returns photoCount (usable photos, the same count write_story_for_section will attach) and gifGenerated for each item, plus willMakeGif when 2 or more photos mean a GIF is made automatically. Event bounds are inclusive dates.
publication_id: string
kind: string (optional)
limit: integer (optional)
events_from: string (optional)
events_to: string (optional)
events_before: string (optional)
cursor: string (optional)
browse_placesLocal restaurants and businesses from the publication's directory (Google listing details, photos). Each item includes photoCount and gifGenerated. kind: restaurant or business. photoCount counts usable photos; willMakeGif is true when there are 2 or more.
publication_id: string
kind: restaurant | business (optional)
search: string (optional)
city: string (optional)
page: integer (optional)
browse_listingsCached real-estate listings for a publication, newest first: listing_id, address, price, beds/baths, price_band, url, photo_count (usable photos), eligible (price floor, editorial policy, freshness; ineligible_reasons says why not), passes_photo_gate (enough photos for a Featured Home) and rank (1 is what add_featured_home picks by default). Uses the same rules as full generation. Pass issue_date (YYYY-MM-DD) to judge freshness as of that issue.
publication_id: string
limit: integer (optional)
eligible_only: boolean (optional)
issue_date: string (optional)
browse_jobsJobs from the publication's connected jobs board, the same feed the builder's Jobs section uses, newest first: job_id, title, company, salary, job_type, city, url, apply_url, logo_url, posted_at, placement, description. Never silently empty: when the feed is off, unconfigured or down, feed_status is disabled or unavailable and reason says exactly what is off and how to turn it on. Add one with add_job and its job_id.
publication_id: string
limit: integer (optional)
search: string (optional)
city: string (optional)
read_linkRead any web page and have lightbreak write it up for a section, using that section's writing instructions. Returns the written headline and text (and event details for event sections). Does not add it; follow with add_story or add_event.
issue_id: string
section_id: string
url: string
list_sourcesThe websites, RSS feeds and calendars lightbreak collects content from for a publication, including last fetch time, status, error, and item count.
publication_id: string
add_sourceAdd a website, RSS feed or calendar for lightbreak to collect content from. bucket: news, events, pets, jobs...
publication_id: string
url: string
name: string (optional)
bucket: string (optional)
Writing and editing
set_section_visibilityHide or show one section on one issue. Hiding requires a reason and never changes the saved template.
issue_id: string
section_id: string
hidden: boolean
reason: string (optional)
write_story_for_sectionHave lightbreak write one story in a section's own voice from a title, summary and source link (for example an item from browse_content or browse_places), then add it to the section. When content_item_id is given the item's photos are attached automatically, as the builder does: a pet, restaurant or business with 2 or more usable photos gets a photo GIF (up to 5 frames; a section with photoGif off, or photoGifCount, changes that), one photo is used as is, and none is refused before anything is written. Other items get their single image unless the section turns source images off. News sections never get an image, and include_image false attaches none anywhere. image_url overrides all of this. The Read more link a reader sees is never a provider widget or a link carrying a key (a pet's Petango popup): the item's own public page is used, and with none the button is left out. Returns imageCount, gifGenerated, gifFrameCount and photosFound. mode keep adds it as written instead of rewriting.
issue_id: string
section_id: string
title: string
source_url: string
summary: string (optional)
content_item_id: string (optional)
image_url: string (optional)
include_image: boolean (optional)
mode: rewrite | keep (optional)
add_storyAdd a story you wrote to a section: headline, paragraphs (**bold** and *italic* work), optional photo and Read more link. Uses the same layout the builder uses. For Restaurant Radar or Business Brief, include the exact place name in a paragraph and pass business_name, or use its directory content_item_id; the first mention becomes bold. Pass content_item_id (from browse_content or browse_places) to attach that item's photo or photo GIF automatically, exactly as write_story_for_section does. An explicit image_url uses that image instead of making a GIF.
issue_id: string
section_id: string
headline: string
paragraphs: string[]
source_url: string (optional)
button_label: string (optional)
image_url: string (optional)
image_alt: string (optional)
content_item_id: string (optional)
business_name: string (optional)
include_image: boolean (optional)
add_eventAdd one of the publication's 6-8 event cards. The event link must resolve. Set featured=true on exactly one event; its featured card can be text-only when no image_url exists. Featuring a new one un-features the previous one. starts_at may be a date (2026-09-27, no time shown), a local time with no offset (2026-09-27T12:30, read as the publication's own time) or any instant with any offset (2026-09-27T17:30:00Z); ends_at is optional and must be later than starts_at. Dates are stored in the publication's timezone. Event sections re-sort by date and drop events outside the issue window. Returns imageCount for the added event.
issue_id: string
section_id: string
title: string
starts_at: string
venue: string
url: string
ends_at: string (optional)
blurb: string (optional)
time_label: string (optional)
type: string (optional)
city: string (optional)
image_url: string (optional)
featured: boolean (optional)
add_jobAdd a job listing to a section. Pass job_id from browse_jobs to fill title, company, salary, job_type, city, link, apply link and logo from the feed (anything you pass yourself wins); otherwise title, company and url are required. Set featured=true on exactly one of the publication's 4-6 jobs; featuring a new one un-features the previous one.
issue_id: string
section_id: string
job_id: string (optional)
title: string (optional)
company: string (optional)
url: string (optional)
salary: string (optional)
job_type: string (optional)
city: string (optional)
apply_url: string (optional)
logo_url: string (optional)
featured: boolean (optional)
update_eventChange fields of an event already in the issue (block id from get_issue). Only the fields you pass change; an empty string clears ends_at, blurb, time_label, type, city or image_url. starts_at and ends_at follow add_event's date rules. A moved start keeps the old end if it is still later; otherwise supply a new end or clear it. The featured flag is untouched (use set_featured), and an agent-created featured card updates with the event. update_block_text cannot edit events.
issue_id: string
block_id: string
title: string (optional)
starts_at: string (optional)
ends_at: string (optional)
venue: string (optional)
url: string (optional)
blurb: string (optional)
time_label: string (optional)
type: string (optional)
city: string (optional)
image_url: string (optional)
update_jobChange fields of a job already in the issue (block id from get_issue). Only the fields you pass change; an empty string clears salary, job_type, city, apply_url or logo_url. The featured flag is untouched (use set_featured).
issue_id: string
block_id: string
title: string (optional)
company: string (optional)
url: string (optional)
salary: string (optional)
job_type: string (optional)
city: string (optional)
apply_url: string (optional)
logo_url: string (optional)
add_featured_homeFill the issue's Featured Home section exactly as full generation does: a photo-tour GIF plus a linked info panel for one listing, replacing whatever is in the section. With no listing_id it uses the best-ranked eligible listing in the publication's featured price band; listing_id (from browse_listings) picks another; zillow_url fetches and caches one first. Returns listing_id, address, price, imageCount (blocks added) and frameCount (GIF frames). Fails with a specific reason (no eligible listing, too few photos, media unavailable) and changes nothing. get_issue shows featuredHome status for the section.
issue_id: string
section_id: string (optional)
listing_id: string (optional)
zillow_url: string (optional)
set_featuredFeature (or un-feature) an event or job that is already in the issue, by block id from get_issue. Featuring clears any other featured item of the same kind in that section, so there is only ever one; the response lists what it cleared (cleared_featured_block_ids). A featured event can have a text-only card when it has no photo; image_url is optional. get_issue shows featured on every event and job.
issue_id: string
block_id: string
featured: boolean (optional)
image_url: string (optional)
list_meme_setsThe workspace's saved meme sets, by name (weekly sets are named like "memes 2026-W40"), with how many memes each holds. Optionally one name, or the set for an issue's send week (issue_id).
name: string (optional)
issue_id: string (optional)
add_meme_setPut a saved meme set's memes into the issue's Meme section (replacing what was there from a set). With no name it uses the set named for the issue's send week ("memes <ISO week>", e.g. "memes 2026-W40"); you never choose individual memes. Pass name or set_id to use a different set.
issue_id: string
name: string (optional)
set_id: string (optional)
update_block_textReplace the words of one text block (headline, paragraph, list) by block id. Keeps the block's link, preserves Local Lowdown labels and an existing bold business name, and converts **bold** and *italic* in the new text into real formatting.
issue_id: string
block_id: string
text: string
update_issue_detailsSet the email subject line and/or preview text.
issue_id: string
subject: string (optional)
preview_text: string (optional)
remove_blocksRemove blocks from an issue by id (use get_issue for ids).
issue_id: string
block_ids: string[]
save_issue_treeSave a full block tree. expected_updated_at must be the updatedAt you read; a newer save by someone else returns a conflict instead of overwriting it.
issue_id: string
tree: object
expected_updated_at: string
upload_image_from_urlCopy an image from a public URL into lightbreak's storage and return a stable URL to use in stories. PNG, JPG, WEBP or GIF, under 8 MB.
image_url: string
make_gifMake a photo GIF from 2 to 5 public photo URLs (the builder's GIF Maker, same frames and timing) and add it to a section, at the end or after after_block_id. Returns gifFrameCount and gifUrl. Stories added with content_item_id get their GIF automatically; use this for photos you found yourself.
issue_id: string
section_id: string
image_urls: string[]
alt: string (optional)
link_url: string (optional)
caption: string (optional)
after_block_id: string (optional)
suggest_subject_linesThree subject line and preview text options written from the issue's content by lightbreak's inbox copywriter: one specific hook per option, grounded in the issue, fact-checked. Best first.
issue_id: string
write_introWrite the issue's intro paragraphs from its headlines, in the intro section's voice, and return them as paragraphs. Nothing is saved: add them with add_story or update_block_text. Works even when the intro section is set to keep as written, because it never overwrites that section.
issue_id: string
section_id: string (optional)
Checking and finishing
review_issueStart the Review Issue agent on the saved issue: links, images, a fact check of every story against its source, and a copy read. Poll get_review.
issue_id: string
get_reviewThe latest review of an issue: status, summary, and findings (severity wrong/fix/check/unverified, block id, message, the source's own words, a suggested fix).
issue_id: string
export_issue_htmlThe finished email HTML. format: beehiiv (default, exactly what is pushed to Beehiiv), html (full designed email) or native.
issue_id: string
format: beehiiv | html | native (optional)
push_to_beehiivPut the finished issue into the publication's Beehiiv account as a DRAFT post. It is not sent or scheduled; a person does that in Beehiiv.
issue_id: string
push_to_activecampaignPut the finished issue into ActiveCampaign as a draft campaign. Not sent.
issue_id: string
list_ids: string[] (optional)
mark_issue_doneMark an issue finished (or not) in lightbreak.
issue_id: string
done: boolean (optional)
Notifications
add_notificationHave lightbreak POST a signed JSON message to your https address when something happens. events: issue.generated, issue.generation_failed, review.completed, issue.pushed_to_esp, issue.marked_done. Returns a signing secret once: verify the Lightbreak-Signature header (t=<unix>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">) with it.
url: string
events: [issue.generated | issue.generation_failed | review.completed | issue.pushed_to_esp | issue.marked_done]
description: string (optional)
list_notificationsNotification addresses (webhooks) for this workspace and the events each one receives.
none
test_notificationSend a sample message to one notification address and report what the receiver answered.
notification_id: string
remove_notificationStop sending notifications to an address.
notification_id: string

DNotifications

EventSent when
issue.generatedA draft finished generating
issue.generation_failedGenerating a draft failed
review.completedAn issue review finished
issue.pushed_to_espA draft was pushed to Beehiiv or ActiveCampaign
issue.marked_doneAn issue was marked done

Each message is a JSON POST with id, type, created_at, workspace_id, publication_id, issue_id, data and links. Headers: Lightbreak-Event, Lightbreak-Delivery, and Lightbreak-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">. Up to 3 attempts on timeout, 429 or 5xx. An address that fails 25 times in a row is switched off.

import { createHmac, timingSafeEqual } from "node:crypto";

// header = the Lightbreak-Signature request header, rawBody = the exact bytes received
export function isFromLightbreak(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const expected = Buffer.from(createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"));
  const given = Buffer.from(parts.v1 ?? "");
  return expected.length === given.length && timingSafeEqual(expected, given);
}

EExamples

POST https://app.lightbreak.ai/api/mcp
Authorization: Bearer lbk_YOUR_KEY
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"review_issue","arguments":{"issue_id":"ISSUE_ID"}}}
curl -X POST https://app.lightbreak.ai/api/agent/list_issues \
  -H "Authorization: Bearer lbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"limit": 5}'

FRules for agents

  1. Run review_issue before push_to_beehiiv or push_to_activecampaign, and fix every finding marked wrong.
  2. Never state that a business is new, opening or long-established unless the source says so. Copy phone numbers, emails and addresses exactly.
  3. Treat a 409 conflict as a person's edit: read the issue again, then reapply your change.
  4. Tell the person which drafts you pushed. Sending always happens in their email platform.
  5. Prefer notifications (add_notification) over polling for long jobs.

GMachine-readable files

/agents.md (this reference as markdown) · /llms.txt (site summary) · https://app.lightbreak.ai/api/agent/openapi (OpenAPI 3.1, needs a key)

lightbreak is a product of 8th replay.

We build software for local media, and we run 20+ local publications on it ourselves. If lightbreak fits, its siblings might too.

8threplay.com
larkfeed

The AI-native platform for local media.

  • News, businesses, events and jobs on one site
  • AI does the grunt work, you approve
  • Built to be found and cited by AI search
Visit larkfeed.com
greetform

Smart questions and matched offers, right after someone subscribes.

  • Smart signup questions that sort every reader
  • Offer wall and inline lead capture
  • A live media kit for sponsors
Visit greetform.com