API v1

LivePublicator API

Draft and publish press releases for your clients. JSON over HTTPS. The full spec is at /api/v1/openapi.json (OpenAPI 3.1).

Keys and the plan

Get a key in your account ($99 a month, 10 release credits a month, see the plan). Send it on every call:

Authorization: Bearer lp_live_...

Draft and publish

1. Send the finished release. We check it and keep the text as you sent it (we do not write or change it):

curl https://livepublicator.com/api/v1/releases \
  -H "Authorization: Bearer $LP_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-launch-1" \
  -d '{
    "company_name": "Acme Robotics",
    "company_domain": "acmerobotics.com",
    "client_authorized": true,
    "headline": "Acme Robotics Opens a Second Factory in Austin",
    "summary": "Acme Robotics opens a second factory in Austin, Texas, adding 120 jobs, with production starting in March.",
    "body": "Acme Robotics today opened ... (250 to 1,000 words, text, Markdown or HTML)",
    "key_facts": ["120 new jobs", "Production starts in March 2027"],
    "quote": "Austin gives us the people and space to grow.",
    "quote_name": "Dana Lee",
    "quote_title": "CEO",
    "contact_name": "Sam Park",
    "contact_title": "Head of Communications",
    "contact_email": "press@acmerobotics.com",
    "image_url": "https://acmerobotics.com/press/austin.jpg"
  }'

If something needs fixing, you get 422 with every problem at once, each with the field, what is wrong and how to fix it. Fix them all and send again. Add "validate_only": true to check without creating anything.

{ "error": { "code": "validation", "message": "2 problems to fix. Fix all of them and send the release again.",
  "problems": [
    { "field": "quote", "problem": "Missing.", "fix": "Send one quote from a real person at the company (quote, quote_name, quote_title). More quotes can go in the body." },
    { "field": "contact_email", "problem": "This email doesn't look right.", "fix": "Send the press contact's full email, for example press@acme.com." }
  ] } }

With no problems, the answer has the release id, status: "draft" and a free private preview_url. No image: the page shows a cover card with the logo and headline (never an AI image). An image under 600 px wide, or one we cannot read, never blocks the release: the answer has image_note saying why, and the page uses the cover card.

2. Publish it (1 credit):

curl -X POST https://livepublicator.com/api/v1/releases/RELEASE_ID/publish \
  -H "Authorization: Bearer $LP_KEY" \
  -H "Idempotency-Key: acme-launch-1-publish"

The status becomes live (with live_url), scheduled (if you sent publish_at) or review (a person checks it first).

The same in JavaScript:

const api = (path, init = {}) =>
  fetch("https://livepublicator.com/api/v1" + path, {
    ...init,
    headers: { Authorization: "Bearer " + process.env.LP_KEY, "Content-Type": "application/json", ...init.headers },
  }).then(async (r) => ({ status: r.status, body: await r.json() }));

const draft = await api("/releases", {
  method: "POST",
  headers: { "Idempotency-Key": "acme-launch-1" },
  body: JSON.stringify({ company_name: "Acme Robotics", company_domain: "acmerobotics.com", client_authorized: true, headline, summary, body, key_facts, quote, quote_name, quote_title, contact_name, contact_title, contact_email }),
});
if (draft.status === 422) throw new Error(draft.body.error.problems.map((p) => p.field + ": " + p.fix).join("
"));
const r = draft.body;
const published = await api("/releases/" + r.id + "/publish", { method: "POST", headers: { "Idempotency-Key": "acme-launch-1-publish" } });
console.log(published.body.status, published.body.live_url);

Endpoints

POST /releasesCreate a draft from your finished release: company_name, company_domain, headline, summary (one sentence), body (250 to 1,000 words), key_facts (1 to 8), quote with quote_name and quote_title, contact_name, contact_title, contact_email; optional faq, contact_phone, logo_url, publish_at (ISO 8601, up to 60 days ahead), and a cover image as image_url (https) or image_base64 (landscape, 1200 px wide or more, JPEG, PNG or WebP, up to 5 MB). validate_only: true checks without creating. Free.
POST /releases/{id}/publishPublish a draft. 1 credit.
GET /releases/{id}Status, preview_url, live_url and notes from the content check.
GET /releases?limit=20Your releases, newest first (up to 100).
GET /creditsCredits left, the next renewal, plan status, releases published today.

Errors

Errors are JSON with a code, a message and, for 422, the field names:

{ "error": { "code": "validation", "message": "Some fields need fixing.", "fields": { "company_domain": "Required: the company's public website, e.g. acme.com." } } }

Rate limits

Each key can make 120 calls a minute. Every answer says where you are, so you can slow down before you hit the limit:

Webhooks

Add a webhook URL (https) to a key in your account. We send release.published and release.held (waiting for a person, or not published), and try again for about an hour if your server does not answer 2xx.

{ "id": "delivery id", "event": "release.published", "created_at": "2026-10-05T09:00:00Z", "data": { "id": "...", "status": "live", "live_url": "..." } }

Check the X-LivePublicator-Signature header (t=unix seconds,v1=hex): an HMAC-SHA256 of t + "." + raw body with the key's webhook secret.

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

function verify(rawBody, header, secret) {
  // header: "t=1759600000,v1=<64 hex characters>"
  const { t, v1 } = Object.fromEntries((header ?? "").split(",").map((part) => part.split("=")));
  if (!t || !v1 || v1.length !== 64 || Math.abs(Date.now() / 1000 - Number(t)) > 600) return false;
  const want = createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}

MCP server for AI tools

The same releases as tools for AI assistants: create_release, update_release, add_image, publish_release, get_release_status, list_releases, fix_live_release, get_account, and preview_press_release (no sign-in). Server URL: https://livepublicator.com/api/mcp (Streamable HTTP). In Claude, ChatGPT and other assistants, add it as a connector and sign in (OAuth): see AI assistants. In your own code it also takes your API key in the Authorization header. The same credits and limits apply, and publishing always uses a credit, so keep the key private.

Claude Code:

claude mcp add --transport http livepublicator https://livepublicator.com/api/mcp --header "Authorization: Bearer lp_live_..."

Claude Desktop (Settings, Developer, Edit config), through the mcp-remote bridge:

{
  "mcpServers": {
    "livepublicator": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://livepublicator.com/api/mcp", "--header", "Authorization: Bearer lp_live_..."]
    }
  }
}

ChatGPT and the OpenAI API: in the Responses API or the Agents SDK, add an MCP tool with server_url set to the URL above and headers set to { "Authorization": "Bearer lp_live_..." }. Connectors inside the ChatGPT app ask for a sign-in (OAuth), which we don't offer yet: use the API route for now.

By using the API you accept the API terms. Questions: use the contact page.