# lightbreak for AI agents

> lightbreak builds local email newsletters. AI agents (Claude, Codex, Grok, Meta Muse, any MCP client or HTTP caller) can operate it with a workspace API key: 35 actions to build issues, have lightbreak write each section in its own voice, fact-check everything against sources, and push drafts to Beehiiv or ActiveCampaign. lightbreak never sends email.

Human-readable page: https://lightbreak.ai/agents

## Connection

- MCP endpoint: https://app.lightbreak.ai/api/mcp
- Transport: MCP Streamable HTTP, JSON responses. Protocol 2025-06-18 (also 2024-11-05, 2025-03-26).
- Authentication: `Authorization: Bearer lbk_...` (workspace API key, made by a workspace admin at Settings > AI agents & API, shown once, revocable).
- Plain HTTPS: `POST https://app.lightbreak.ai/api/agent/<action>` with the action's arguments as a JSON body.
- OpenAPI 3.1: https://app.lightbreak.ai/api/agent/openapi (send the key).
- Scope: one workspace, newsletter-manager level, optionally limited to chosen publications. Keys cannot manage people or keys and cannot open lightbreak's screens.
- Errors: HTTP status with `{"error": "plain-English sentence"}`. Over MCP the tool result has `isError: true`.
- Saving: `save_issue_tree` requires `expected_updated_at`; a newer save by someone else returns 409 instead of overwriting.
- Email: never sent. Push actions create drafts only.

## Recommended 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_issue to read it as an outline with block ids and each section's writing instructions.
4. Edit: update_block_text for a single block's words, 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).
5. review_issue, then poll get_review. The reviewer checks every link, image, fact (against each story's source) and sentence. Findings marked "wrong" include the source's own words. Fix what it finds and review again.
6. export_issue_html, or push_to_beehiiv / push_to_activecampaign when the issue is ready. mark_issue_done marks it finished in lightbreak.
7. Instead of polling, add_notification to be told when a draft is generated, a review finishes, or a draft reaches the ESP.

## Actions (35)

### Newsletters and issues

- `list_publications`: List the newsletters (publications) this key can work on: id, name, slug, locale and timezone.
- `list_issues`: List recent issues, newest first. Optionally only one publication.
  - optional: publication_id: string; limit: integer
- `list_templates`: Saved issue templates for a publication (or the whole workspace with scope=workspace).
  - optional: publication_id: string; scope: publication | workspace
- `create_issue`: Create an empty issue for a date from the publication's template. Returns the issue id (or the existing one for that date).
  - required: publication_id: string; date: string
- `generate_issue`: Have 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.
  - required: publication_id: string
  - optional: date: string
- `get_generation_status`: Check a generate_issue run. When finished it includes the issue id.
  - required: publication_id: string; run_id: string
- `get_issue`: Read an issue as an outline: subject, preview text, updatedAt, and each section's writing instructions and blocks (id, kind, text, url, image).
  - required: issue_id: string
- `get_issue_tree`: Read the full raw block tree of an issue, for structural edits with save_issue_tree.
  - required: issue_id: string
- `delete_issue`: Delete an issue.
  - required: issue_id: string

### Finding content

- `browse_content`: Stories, events and other items lightbreak has already collected from a publication's sources, newest first. kind: news, events, pets, jobs, tools, youtube, business, restaurants.
  - required: publication_id: string
  - optional: kind: string; limit: integer; events_from: string; events_before: string; cursor: string
- `browse_places`: Local restaurants and businesses from the publication's directory (Google listing details, photos). kind: restaurant or business.
  - required: publication_id: string
  - optional: kind: restaurant | business; search: string; city: string; page: integer
- `read_link`: Read 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.
  - required: issue_id: string; section_id: string; url: string
- `list_sources`: The websites, RSS feeds and calendars lightbreak collects content from for a publication.
  - required: publication_id: string
- `add_source`: Add a website, RSS feed or calendar for lightbreak to collect content from. bucket: news, events, pets, jobs...
  - required: publication_id: string; url: string
  - optional: name: string; bucket: string

### Writing and editing

- `write_story_for_section`: Have lightbreak write one story in a section's own voice from a title, summary and source link (for example an item from browse_content), then add it to the section. mode keep adds it as written instead of rewriting.
  - required: issue_id: string; section_id: string; title: string; source_url: string
  - optional: summary: string; content_item_id: string; image_url: string; mode: rewrite | keep
- `add_story`: Add 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.
  - required: issue_id: string; section_id: string; headline: string; paragraphs: string[]
  - optional: source_url: string; button_label: string; image_url: string; image_alt: string
- `add_event`: Add an event card to a section. starts_at is ISO 8601 with offset. Event sections re-sort by date and drop events outside the issue window (you are told if that happens).
  - required: issue_id: string; section_id: string; title: string; starts_at: string; venue: string; url: string
  - optional: blurb: string; time_label: string; type: string; city: string; image_url: string
- `add_job`: Add a job listing to a section.
  - required: issue_id: string; section_id: string; title: string; company: string; url: string
  - optional: salary: string; job_type: string; city: string; apply_url: string; logo_url: string
- `update_block_text`: Replace the words of one text block (headline, paragraph, list) by block id. Keeps the block's link and formatting.
  - required: issue_id: string; block_id: string; text: string
- `update_issue_details`: Set the email subject line and/or preview text.
  - required: issue_id: string
  - optional: subject: string; preview_text: string
- `remove_blocks`: Remove blocks from an issue by id (use get_issue for ids).
  - required: issue_id: string; block_ids: string[]
- `save_issue_tree`: Save 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.
  - required: issue_id: string; tree: object; expected_updated_at: string
- `upload_image_from_url`: Copy 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.
  - required: image_url: string
- `suggest_subject_lines`: Three subject line and preview text options written from the issue's content.
  - required: issue_id: string
- `write_intro`: Write the issue's intro paragraph from its headlines, in the intro section's voice.
  - required: issue_id: string
  - optional: section_id: string

### Checking and finishing

- `review_issue`: Start 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.
  - required: issue_id: string
- `get_review`: The 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).
  - required: issue_id: string
- `export_issue_html`: The finished email HTML. format: beehiiv (default, exactly what is pushed to Beehiiv), html (full designed email) or native.
  - required: issue_id: string
  - optional: format: beehiiv | html | native
- `push_to_beehiiv`: Put 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.
  - required: issue_id: string
- `push_to_activecampaign`: Put the finished issue into ActiveCampaign as a draft campaign. Not sent.
  - required: issue_id: string
  - optional: list_ids: string[]
- `mark_issue_done`: Mark an issue finished (or not) in lightbreak.
  - required: issue_id: string
  - optional: done: boolean

### Notifications

- `add_notification`: Have 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.
  - required: url: string; events: [issue.generated | issue.generation_failed | review.completed | issue.pushed_to_esp | issue.marked_done]
  - optional: description: string
- `list_notifications`: Notification addresses (webhooks) for this workspace and the events each one receives.
- `test_notification`: Send a sample message to one notification address and report what the receiver answered.
  - required: notification_id: string
- `remove_notification`: Stop sending notifications to an address.
  - required: notification_id: string

## Notifications (webhooks)

Events:

- `issue.generated`: A draft finished generating
- `issue.generation_failed`: Generating a draft failed
- `review.completed`: An issue review finished
- `issue.pushed_to_esp`: A draft was pushed to Beehiiv or ActiveCampaign
- `issue.marked_done`: An issue was marked done

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

```js
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);
}
```

## Examples

```http
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"}}}
```

```bash
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}'
```

## Rules 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 ESP.
5. Prefer notifications (`add_notification`) over polling for long jobs.
