Articles API

A URL in. A published audio article out.

Send a page URL or your own title and text; Everlit extracts, narrates in your publication’s voice with byline, disclaimer and music, and publishes to a public player you can link or embed. The full Studio pipeline, from code or from an AI agent over MCP.

Quickstart

# 0. Which publication? (one-time — the id is stable)
curl https://api.everlit.audio/v1/publications \
  -H "Authorization: Bearer api_YOUR_KEY"
# => { "object": "list", "data": [ { "id": "pblc_abc123", "name": "The Daily", ... } ] }

# 1. Create an article from a URL
curl -X POST https://api.everlit.audio/v1/articles \
  -H "Authorization: Bearer api_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"publication":"pblc_abc123","url":"https://example.com/news/city-budget"}'
# => 202
# {
#   "object": "article", "id": "art_01jx...", "status": "queued",
#   "url": "https://example.com/news/city-budget", "title": "City approves budget", ...
# }

# 2. Poll until status is "succeeded" (typically 1-5 minutes)
curl https://api.everlit.audio/v1/articles/art_01jx... \
  -H "Authorization: Bearer api_YOUR_KEY"
# => { ..., "status": "succeeded", "article_id": "artl_9f3k...",
#      "player_url": "https://everlit.audio/embeds/artl_9f3k...",
#      "embed_html": "<iframe src=\"https://everlit.audio/embeds/artl_9f3k...\" ...></iframe>",
#      "duration_seconds": 254.3 }

Or send the content yourself — no fetch, deterministic input:

curl -X POST https://api.everlit.audio/v1/articles \
  -H "Authorization: Bearer api_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "publication": "pblc_abc123",
    "url": "https://example.com/news/city-budget",
    "title": "City approves budget",
    "text": "The city council voted 7-2 on Tuesday to approve...",
    "authors": ["Jane Doe"],
    "published_at": "2026-09-01T10:30:00Z"
  }'

Client examples

Python

import time, requests

API = "https://api.everlit.audio/v1"
H = {"Authorization": "Bearer api_YOUR_KEY"}

pub = requests.get(f"{API}/publications", headers=H).json()["data"][0]["id"]
job = requests.post(f"{API}/articles", headers=H,
                    json={"publication": pub, "url": "https://example.com/news/city-budget"}).json()

while job["status"] in ("queued", "processing"):
    time.sleep(15)
    job = requests.get(f"{API}/articles/{job['id']}", headers=H).json()

if job["status"] == "succeeded":
    print(job["player_url"])
    print(job["embed_html"])
else:
    print("failed:", job["error"])

Node

const API = "https://api.everlit.audio/v1";
const H = { Authorization: "Bearer api_YOUR_KEY", "Content-Type": "application/json" };

let job = await (await fetch(`${API}/articles`, {
  method: "POST", headers: H,
  body: JSON.stringify({ publication: "pblc_abc123", url: "https://example.com/news/city-budget" }),
})).json();

while (["queued", "processing"].includes(job.status)) {
  await new Promise(r => setTimeout(r, 15000));
  job = await (await fetch(`${API}/articles/${job.id}`, { headers: H })).json();
}
console.log(job.status === "succeeded" ? job.player_url : job.error);

Authentication

Every request needs an api_ key as a bearer token:

Authorization: Bearer api_...

Keys are minted in Studio under API → Keys and are scoped to your publisher account. They are server-side credentials — never embed one in a browser or mobile app (for reader-facing pages, use the Auto Audio widget instead, which needs no key).

Any paid Studio plan can mint an api_ key. A key may additionally be restricted to specific publications; those keys only see and publish into their publications. Unlike the TTS API, the Articles API needs no pay-as-you-go card: articles count against your plan’s monthly article quota, exactly as widget- and Studio-created articles do. (The same key calls the TTS API only once pay-as-you-go billing is set up; until then TTS requests answer 403 tts_api_not_enabled.)

Endpoints

Method Path Purpose
POST /v1/articles Create an article → 202 (or 200 when it already exists)
GET /v1/articles/{id} Fetch an article job (poll this)
GET /v1/articles List article jobs, newest first (limit, starting_after, status, publication, q)
DELETE /v1/articles/{id} Delete the article, or cancel a running conversion → 204
GET /v1/publications The publications this key can publish into
GET /v1/voices Voices available to your account (shared with the TTS API)
POST /v1/mcp MCP server for AI agents — see MCP

Create article

POST /v1/articles

Send url (we fetch and extract the page) or title + text (we use your content verbatim and never fetch). When both are sent, url is only the article’s canonical identifier: it dedupes repeat requests and is what the player links back to.

Parameter Type Required Description
publication string yes Publication id (pblc_...) from GET /v1/publications. Supplies the voice, music, disclaimer and player defaults.
url string * Public http(s) URL of the article.
title string * Headline. With text, skips fetching.
text string * Article body, plain text, up to 200,000 characters. Markup is stripped.
authors string[] no Author names; read aloud as the byline and added as author: tags.
summary string no Description shown on the player.
category string no Section name; becomes a category: tag.
tags string[] no Extra tags (e.g. tickers).
art_url string no Cover image URL. Extracted from the page when omitted.
published_at string no ISO 8601. Defaults to the page’s date, else now. A future time schedules the article: it stays unlisted until then.
custom_byline string no Exact byline text to read instead of the generated “A and B”.
voice string no Narrator voice id (GET /v1/voices). Default: the publication’s voice for the detected language.
guest_voice string no Second voice for conversation mode.
conversation_mode boolean no Two-voice conversational narration.
mix boolean no Apply the publication’s intro/outro music beds. Default true.
intro_mixable / outro_mixable string no Mixable ids overriding the publication’s beds.
read_urls / read_alt_text boolean no Read URLs / image alt text aloud. Default: publication setting.
read_author boolean no Read the byline aloud. Default: publication setting (usually true).
sonic_optimizer boolean no Audio Polish pass. Default: publication setting.
disclaimer / disclaimer_voice string no Override the publication’s AI disclaimer text / voice.
regenerate boolean no Re-narrate even if an article for this url already exists (see Existing articles).
callback_url string no https:// URL to POST the finished article to.
metadata object no Up to 8 string values (512 bytes total), echoed back on the job.

Unknown parameters are rejected with 400 unknown_parameter — the API is strict so that future additions are never breaking.

When url is given without text, the page is fetched during the request (a few seconds). A page we cannot fetch or that has no article body returns 422 content_unavailable / 422 no_content — send title and text instead.

The article resource

Returned by every endpoint and delivered to your callback — one shape:

{
  "object": "article",
  "id": "art_01jx...",
  "status": "succeeded",
  "created_at": "2026-09-01T14:00:00Z",
  "completed_at": "2026-09-01T14:03:12Z",
  "publication": "pblc_abc123",
  "url": "https://example.com/news/city-budget",
  "title": "City approves budget",
  "language": "en",
  "voice": "109",
  "characters": 4812,
  "article_id": "artl_9f3k...",
  "player_url": "https://everlit.audio/embeds/artl_9f3k...",
  "embed_html": "<iframe title=\"Everlit Audio Player\" src=\"https://everlit.audio/embeds/artl_9f3k...\" width=\"100%\" height=\"136px\" ...></iframe>",
  "duration_seconds": 254.3,
  "regenerate": false,
  "deleted_at": null,
  "callback_url": null,
  "error": null,
  "metadata": { "ref": "cms-4512" }
}
  • id (art_...) is this conversion job. article_id (artl_...) is the published Everlit article — the durable thing your readers listen to, present once status is succeeded.
  • player_url is the public player page; embed_html is the same player as an iframe for your site. Both stay valid for the life of the article.
  • status is one of queued, processing, succeeded, failed, deleted. Poll every 15–30 seconds; a typical article completes in 1–5 minutes (longer for very long pieces). error.code explains a failure.

Existing articles

url is the identity of an article within a publication. If a completed article already exists for it — created by the API, the widget on your site, RSS ingestion, or Studio — POST /v1/articles returns it immediately with 200, status: "succeeded", and its player_url, without narrating again or spending quota. If a conversion for that URL is already running, you get that running job back (200, status: "queued" or "processing").

Pass regenerate: true to re-narrate: the article keeps its article_id and player URL, and the new audio replaces the old when it finishes. Use it when the page’s text changed or the voice settings did. Text-only requests (no url) always create a new article.

Deleting articles

DELETE /v1/articles/{id} answers 204 and does one of two things:

  • A finished job: the published article is taken down — player_url and embed_html stop working, the article disappears from Studio and from any playlists it was in. Other jobs that returned the same article_id (see Existing articles) are marked deleted with it.
  • A running job (queued or processing): the conversion is cancelled and no article is published. Cancelling a regenerate: true job keeps the existing article and its old audio.

The job record survives with status: "deleted" and a deleted_at timestamp, so GET /v1/articles/{id} and the list still show it (filter with status=deleted). Deleting is idempotent and never refunds the month’s article quota. Deleting an article does not stop the Auto Audio widget from narrating that page again the next time readers open it — remove the widget from the page, or delete the article from Studio and set the page to skip, if that is what you want.

Publications

GET /v1/publications

{
  "object": "list",
  "data": [
    {
      "object": "publication",
      "id": "pblc_abc123",
      "name": "The Daily",
      "url": "https://example.com",
      "default_voice": "109",
      "language_voices": ["english", "spanish"]
    }
  ],
  "has_more": false
}

A publication carries everything an article inherits: default narrator per language, guest voice, intro/outro beds, disclaimer text and voice, reading toggles, and the player’s look. Configure them in Studio under Publications → Auto Audio; the API only overrides what you pass.

Quotas and rate limits

  • Articles per month follow your plan, shared with the widget and Studio. Over the cap, POST /v1/articles answers 403 article_limit_reached. Requests that converge onto an existing article never spend quota.
  • Requests: 60 creates and 300 reads per minute per key, with RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers on every response and Retry-After on a 429.
  • Capacity: when the conversion queue is saturated you get 503 capacity_unavailable with Retry-After — our capacity, not your behaviour; retry after the delay.
  • Per URL: one conversion at a time. A second request while one is in flight returns the running job; a concurrent widget-triggered conversion answers 409 conversion_in_progress.

Idempotency

Pass an Idempotency-Key header (any unique string, e.g. a UUID) on POST /v1/articles to make retries safe: resubmitting the same key with the same body returns the original job (200 + Idempotency-Replayed: true) without creating another. The same key with a different body is rejected with 409 idempotency_key_reuse. Keys are scoped to your account. For URL-based requests the URL itself already dedupes (see Existing articles); the key matters most for text-only input.

Webhooks

Pass callback_url and we POST the full article resource to it when the job reaches a terminal state, with header X-Everlit-Event: article.succeeded or article.failed. Deliveries retry with backoff for ~30 minutes on non-2xx responses. Only https:// URLs are accepted.

Deliveries are signed exactly like TTS API webhooks — X-Everlit-Signature: t=<unix>,v1=<hmac> over "<t>.<body>" with your webhook secret from Studio → API → Usage. See the TTS API webhook section for verification code; one verifier handles both event families.

MCP

The Articles API is part of Everlit’s MCP server, so Claude, Cursor, Claude Code, and any other MCP-capable agent can publish audio articles directly. The server also hosts the TTS API tools; one connection, one key.

Tool Backs
list_publications GET /v1/publications
create_article POST /v1/articles (accepts an idempotency_key argument)
get_article GET /v1/articles/{id}
list_articles GET /v1/articles
list_voices GET /v1/voices

Endpoint: https://api.everlit.audio/v1/mcp — Streamable HTTP transport, stateless (no session ids), JSON responses. Authenticate every request with the same Authorization: Bearer api_... header; there is no OAuth flow.

{
  "mcpServers": {
    "everlit": {
      "type": "http",
      "url": "https://api.everlit.audio/v1/mcp",
      "headers": { "Authorization": "Bearer api_YOUR_KEY" }
    }
  }
}
claude mcp add --transport http everlit https://api.everlit.audio/v1/mcp \
  --header "Authorization: Bearer api_YOUR_KEY"

A typical agent session: list_publicationscreate_article with the page URL the user gave → get_article until succeeded → hand back player_url. There is no blocking “wait” tool: while a job is queued or processing, create_article and get_article results carry poll_after_seconds (30), and the server instructions tell agents to wait that long between calls. When an article for the URL already exists the tool returns it with converged: true and no conversion runs.

Tool calls are admitted and limited exactly like the REST endpoints — they run the same code. A failed call returns an MCP tool error (isError: true) whose structuredContent.error is the same envelope documented under Errors, plus http_status and, on 429/503, retry_after seconds.

Tools

Every tool takes a JSON object of arguments and returns the same JSON the matching REST endpoint would, as structuredContent (and pretty-printed as text). create_article and get_article add poll_after_seconds while the job is still running.

create_article

Publish a full Everlit audio article from a web page (url) or from text (title + text): narration in the publication’s configured voice, intro/outro music, byline, disclaimer, and a public player. Returns the article job (status “queued”); poll get_article (honoring poll_after_seconds) until status is “succeeded”, then use player_url / embed_html. If an article for the url already exists it is returned immediately with status “succeeded” — pass regenerate: true only to re-narrate. Counts against the monthly plan article quota.

Argument Type Required Description
publication string yes The publication id (pblc_…) from list_publications.
url string no Public http(s) URL of the article to narrate. We fetch and extract it. Required unless title and text are given.
title string no Article headline (with text, skips fetching the url).
text string no Article body to narrate, plain text (up to 200,000 characters).
authors array no Author names, read aloud as the byline.
summary string no Short description shown on the player.
category string no Section/category name.
tags array no Extra tags.
art_url string no Cover image URL.
published_at string no ISO 8601 publication time; defaults to now.
voice string or integer no Narrator voice id from list_voices. Defaults to the publication’s voice for the detected language.
conversation_mode boolean no Two-voice conversational narration.
mix boolean no Apply the publication’s intro/outro music beds (default true).
regenerate boolean no Re-narrate even if an article for this url already exists.
callback_url string no Optional https:// URL to POST the finished article to.
metadata object no Up to 8 string values (512 bytes total), echoed back on the article.
idempotency_key string no Any unique string; resubmitting the same key and arguments returns the original article job.

get_article

Fetch an article job by id. status is one of queued, processing, succeeded, failed, deleted. While queued/processing the result includes poll_after_seconds: wait that long before checking again. When succeeded, player_url is the public player page and embed_html an iframe for a website.

Argument Type Required Description
id string yes The article job id (art_…).

list_articles

List this account’s article jobs, newest first. Paginate with starting_after = the last id of the previous page while has_more is true.

Argument Type Required Description
limit integer no Page size (default 20).
starting_after string no Return jobs created before this job id.
status enum: queued, processing, succeeded, failed, deleted no Filter by status.
publication string no Filter by publication id.
q string no Free-text match on id, url, title, article id, error code, or metadata.

list_publications

Publications this key can publish articles into, with each one’s site URL and default voice. Use the id as create_article’s publication.

No arguments.

delete_article

Delete an article: cancels it if still queued/processing, otherwise takes the published player and embed down. The job record stays with status “deleted”. Quota already used is not refunded.

Argument Type Required Description
id string yes The article job id (art_…).

Errors

Errors use one envelope, with a stable machine-readable code — branch on code, never on message text:

{
  "error": {
    "type": "invalid_request_error",
    "code": "content_unavailable",
    "message": "The article could not be fetched from that URL. Check that it is public, or send title and text instead.",
    "param": "url",
    "request_id": "ab12cd34"
  }
}

Include request_id in support tickets.

HTTP code Meaning
400 invalid_json Body is not a JSON object
400 missing_parameter publication absent, or neither url nor title+text given
400 unknown_parameter Unrecognized parameter (check spelling)
400 invalid_parameter Wrong type or value (arrays, booleans, published_at, voice/mixable access)
400 invalid_url url / art_url is not an http(s) URL
400 invalid_encoding / text_too_long text is not UTF-8 / over 200,000 characters
400 invalid_callback_url Must be https with a hostname
401 missing_api_key / invalid_api_key / api_key_revoked / api_key_expired Credential problems
403 wrong_key_type Use an api_ key (not pk_/tkn_)
403 article_limit_reached Monthly plan quota used up
403 voice_not_accessible / mixable_not_accessible Belongs to another account
404 publication_not_found Not on your account (or outside this key’s publication scope)
404 article_not_found / voice_not_found / mixable_not_found / unknown_endpoint Nothing at that address
409 conversion_in_progress The URL is being converted by another request; retry after Retry-After
409 idempotency_key_reuse Same key, different body
422 content_unavailable The page could not be fetched
422 no_content / no_speakable_text Nothing narratable on the page / in text
429 rate_limit_exceeded / read_rate_limit_exceeded Slow down (see Retry-After)
500 internal_error Our fault
503 capacity_unavailable Temporarily saturated; retry

A job that fails after being accepted reports it on the resource instead: status: "failed" with error.code one of no_content, generation_failed, mixing_failed, storage_error, publish_failed, article_failed, timeout, internal_error. Failed jobs never spend quota.

Changelog

1.1.0 — 2026-09-02

  • DELETE /v1/articles/{id} and the delete_article MCP tool: soft-delete a published article or cancel a running conversion. New deleted status and deleted_at field on the article resource.

1.0.0 — 2026-09-01

  • Initial release: POST /v1/articles, GET /v1/articles/{id}, GET /v1/articles, GET /v1/publications, webhooks, idempotency, and the create_article / get_article / list_articles / list_publications MCP tools on the shared Everlit MCP server.

Support

  • Email: support@everlit.audio
  • OpenAPI spec: https://everlit.audio/docs/tts-openapi.yml (shared with the TTS API)
  • Status: https://status.everlit.audio