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. Create a draft from a brief (we write it) or your full text (we check and format 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",
    "brief": "Acme Robotics opens a second factory in Austin, Texas, adding 120 jobs. Production starts in March.",
    "quote": "Austin gives us the people and space to grow.",
    "quote_name": "Dana Lee",
    "quote_title": "CEO, Acme Robotics"
  }'

The answer has the release id, status: "drafting" and a private preview_url. Writing takes about a minute. Check it with GET /releases/{id} until the status is draft.

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", brief: "Acme Robotics opens a second factory in Austin..." }),
});
let r = draft.body;
while (r.status === "drafting") {
  await new Promise((ok) => setTimeout(ok, 10000));
  r = (await api("/releases/" + r.id)).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: company_name, company_domain, brief or full_text, optional quote with quote_name and quote_title, an image as image_url (https) or image_base64 (JPEG, PNG or WebP, up to 5 MB), publish_at (ISO 8601, up to 60 days ahead). Free. No image: the release goes live without one.
POST /releases/{id}/publishPublish a draft. 1 credit.
GET /releases/{id}Status, preview_url, live_url, notes from the content check, report_due_at and report_url (the AI report, 7 days after it went live).
GET /releases?limit=20Your releases, newest first (up to 100).
GET /creditsCredits left, when they reset, 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." } } }

Webhooks

Add a webhook URL (https) to a key in your account. We send release.published, release.held (waiting for a person, or not published) and report.ready (the AI report of a release, 7 days after it went live), 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 API as tools for AI assistants: draft_release, publish_release, get_release, list_releases, get_credits. Server URL: https://livepublicator.com/api/mcp (Streamable HTTP). It signs in with 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.