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": null, "job_id": "ajob_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/ajob_01jx... \
  -H "Authorization: Bearer api_YOUR_KEY"
# => { ..., "status": "succeeded", "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 by its id (artl_...) or by job_id (ajob_...) (poll this)
GET /v1/articles List articles, newest first (limit, starting_after, status, publication, q, ids, idempotency_key, source)
PATCH /v1/articles/{id} Change title, summary, tags, cover, publish time or privacy → 200
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/publications/{id} One publication and the settings its articles inherit
PATCH /v1/publications/{id} Change those settings
GET /v1/voices Voices available to your account (shared with the TTS API)
GET /v1/mixables Music beds — see Music beds
POST /v1/mixables Upload a music bed of your own → 201
GET /v1/mixables/{id} One music bed
DELETE /v1/mixables/{id} Delete one of your own beds → 204
GET /v1/playlists Playlists in this account — see Playlists
POST /v1/playlists Create a playlist → 201
GET /v1/playlists/{id} One playlist with its resolved tracks and pins
PATCH /v1/playlists/{id} Change the playlist’s rules
DELETE /v1/playlists/{id} Delete the playlist → 204
POST /v1/playlists/{id}/articles Pin an article to a playlist → 201
PATCH /v1/playlists/{id}/articles/{article} Move a pinned article
DELETE /v1/playlists/{id}/articles/{article} Unpin an article
GET /v1/playlists/{id}/feed The playlist’s podcast feed — see Podcast feeds
PATCH /v1/playlists/{id}/feed Set the podcast metadata
POST /v1/playlists/{id}/feed/activate Publish the feed
POST /v1/playlists/{id}/feed/deactivate Stop publishing it
GET /v1/playlists/{id}/feed/platforms Where the show stands on each directory — see Directories
POST /v1/playlists/{id}/feed/platforms/{platform}/submitted Record that the show was submitted there
POST /v1/playlists/{id}/feed/platforms/{platform}/live Record that the directory listed it
DELETE /v1/playlists/{id}/feed/platforms/{platform} Clear what was recorded for one directory → 204
POST /v1/playlists/{id}/feed/ping Ask Overcast to re-crawl the feed now
GET /v1/pronunciations The pronunciation library — see Pronunciations
PUT /v1/pronunciations Add or update a pronunciation rule
DELETE /v1/pronunciations/{word} Remove a pronunciation rule
GET /v1/ingestion-feeds RSS/Atom feeds narrated automatically — see Ingestion feeds
POST /v1/ingestion-feeds Start ingesting a feed → 201
GET /v1/ingestion-feeds/{id} One ingestion feed
PATCH /v1/ingestion-feeds/{id} Change its interval, caps, name or status
DELETE /v1/ingestion-feeds/{id} Stop ingesting and delete it → 204
POST /v1/ingestion-feeds/{id}/poll Check the feed now
GET /v1/ingestion-feeds/{id}/items Items the feed has seen and the articles they became
POST /v1/mcp MCP server for AI agents — see MCP

GET /v1/articles?idempotency_key=K finds a job again if you lose its id before recording it (crash recovery, same as the TTS API); ?ids=artl_a,artl_b (up to 50) fetches many articles you already know the ids of in one call. ?source= chooses jobs, Studio articles, or both — see Any article, any origin.

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. May carry inline markup: [everlit-pause:1.5] for an explicit pause, [everlit-mixable:N] / [everlit-mixable-background:N] for a music bed, [everlit-subvoice-open:N][everlit-subvoice-close] for a passage in a second voice; other Everlit control sequences are 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).
audio_url string no https URL of your own recording to publish instead of narrating — see Pre-recorded audio. Requires title.
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, used when podcast_mode is on.
language string no Language tag (en-US, es-MX) to narrate in, skipping detection. The publication’s voice and disclaimer for that language are used.
podcast_mode boolean no Two-voice conversational narration.
music boolean no Mix the intro/outro music beds into the audio. Default true.
intro_music / outro_music string no The bed to play, overriding the publication’s: a mixable id, "shuffle" for a random track from the publication’s intro/outro music, or "" for none.
intro_music_duration / outro_music_duration number no Seconds of bed before / after the narration.
intro_music_pad / outro_music_pad number no Seconds the bed overlaps the narration.
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).
audio_polish boolean no Audio Polish pass — rewrite the text for the ear before narrating. Default: publication setting.
audio_polish_level string no How far Audio Polish may rewrite: "0", "1", "2", "3". Default: publication setting, else "2".
disclaimer / disclaimer_voice string no Override the spoken AI disclaimer text / voice for this article. disclaimer: "" narrates none. The strings "false", "null", "none" and "off" are rejected with 400 invalid_parameter so they are never read aloud.
ui_disclaimer boolean no Show the AI disclosure line under the player. Default: publication setting.
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.

Every audio field above is the same name a publication uses for its own default — see Publication settings. The names this API launched with (mix, intro_mixable, outro_mixable, sonic_optimizer, conversation_mode) are permanently accepted as aliases; sending both spellings of one setting with different values is a 400. A validation error names the field with the spelling you sent — send music and the error’s param is music, send mix and it is mix.

Blank vs. omitted. This rule holds everywhere in this API, on articles and on publications:

  • omitted, or null — no opinion. The publication default applies, nothing stored is changed, and on a regenerate the article keeps the settings it was last narrated with.
  • "" (empty string) — an explicit off. These are the only fields with an off state:

    Field "" means
    disclaimer (article) narrate no spoken disclaimer
    voices.{lang}.disclaimer (publication) narrate none for that language
    ui_disclaimers.{lang} (publication) show no disclosure line for that language
    intro_music / outro_music (both) play no bed
    language (publication) go back to detecting the language per article
    player.title_intro / player.title_icon (publication) show none

    Anywhere else "" is not an off switch: a voice, an enum or a boolean rejects it with 400, and a blank number of seconds (or audio_polish_level) is treated exactly like null. Publication pauses are the same — "" there means use our default, because a pause has no off: 0 is a real, explicit value.

    For the music beds "" and omitted really are different: omitting intro_music lets us pick a bed and omitting outro_music uses the publication’s, while "" means silence.

The music beds take a third value. intro_music and outro_music each accept a mixable id, the literal "shuffle" — a random track from the publication’s intro/outro music, picked fresh for every narration and never recorded on the article — or "" for no bed at all. A publication reads its beds back in those same three forms, so the defaults object from a GET PATCHes straight back whatever state it is in. An intro with no track set reads as "shuffle", because that is what it plays; an outro with none reads as "".

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.

Pre-recorded audio

Send audio_url — an https link to your own MP3, WAV or M4A — and nothing is narrated: we fetch the file, transcribe it for read-along, mix the publication’s intro/outro beds around it exactly as music, intro_music and outro_music say, and publish it as an ordinary audio article with a player.

  • title is required. text is optional — when you leave it out, the transcript of your recording becomes the article body. url is still the canonical link and the identity of the article (see Existing articles).
  • The narration parameters do not apply and are rejected with 400 invalid_parameter: voice, guest_voice, language, podcast_mode, audio_polish, audio_polish_level, read_urls, read_alt_text, read_author, custom_byline, disclaimer, disclaimer_voice (and their aliases). Everything else — the music beds and their durations and pads, ui_disclaimer, authors, summary, category, tags, art_url, published_at, callback_url, metadata, publication — is accepted.
  • The file must be at most 500MB and served over https from a public host. Every redirect is re-checked, so a link that ends up at a private address is refused with 400 invalid_url.
  • On the finished article, characters and voice are null — nothing was synthesized, and no plan characters are spent (the article still counts against the monthly article quota). duration_seconds is the length of the mixed file, beds included.
  • The spoken AI disclaimer is never narrated over your recording, and the ID3 tags carry the AI disclosure as false. The disclosure line under the player follows ui_disclaimer when you send it, and is hidden when you do not — the same as an upload made in Studio.
  • source is api, as for any other article this API created.
curl https://api.everlit.audio/v1/articles \
  -H "Authorization: Bearer $EVERLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"publication":"pblc_abc123","title":"Episode 12: The bridge","url":"https://example.com/podcast/12","audio_url":"https://cdn.example.com/episodes/12.mp3","authors":["Ana Ruiz"]}'
# => 202 { "object": "article", "job_id": "ajob_01jx...", "status": "queued", "characters": null, ... }

Inline markup

text takes the same inline markup as the TTS API — the tags Studio and the WordPress plugin write — so text copied from either narrates the same way: [everlit-pause:N], [everlit-mixable:N], [everlit-mixable-background:N] and [everlit-subvoice-open:N][everlit-subvoice-close]. The vocabulary, the casing rules and the examples live in one place: Inline markup in the TTS guide.

  • Every id is checked against your account before the article is created: a malformed tag or an unbalanced subvoice block is 400 invalid_markup, a bed or voice belonging to someone else is 403 mixable_not_accessible / 403 voice_not_accessible, and an id that does not exist is a 404. The error’s param is text.
  • Tags are not narrated and are never counted in characters.
  • They apply to narrated text only. On an audio_url article text is the article body, not a script, so markup there is left exactly as you sent it.
  • [Everlit-Sponsored:N] and any other Everlit control sequence are still stripped, from text and from title, summary, custom_byline and disclaimer, which take no inline markup of their own.
  • Audio Polish leaves the tags where you put them.

The article resource

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

{
  "object": "article",
  "id": "artl_9f3k...",
  "job_id": "ajob_01jx...",
  "status": "succeeded",
  "source": "api",
  "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,
  "markup": null,
  "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" },
  "published_at": "2026-09-01T14:03:12Z",
  "privacy": null,
  "tags": [],
  "summary": null,
  "authors": [],
  "cover": null
}
  • id (artl_...) is the published Everlit article — the durable thing your readers listen to. It is null until the article exists, which for a new conversion means when status becomes succeeded.
  • job_id (ajob_...) is the conversion job this API ran, returned by POST /v1/articles and usable as {id} on every route while id is still null. It is null on an article this API did not convert.
  • markup counts the inline markup in the text we narrated (pause_tags, pause_seconds, mixable_tags, subvoice_tags), and is null when there was none.
  • 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.
  • source is api for a job created here and studio for everything else in the account (the widget, WordPress, RSS, Studio itself). A studio row has no job_id, no characters, and its created_at is nothing more than its published_at; privacy, tags, summary, authors and cover come from the published article and are empty on a job that has not published one.

Any article, any origin

Your Studio holds more than this API created: pages the Auto Audio widget narrated, WordPress posts, RSS items, uploads made in Studio. All of them are readable, listable and deletable here by their artl_ article id.

  • GET /v1/articles/{id} and DELETE /v1/articles/{id} take an article id or a job_id. An artl_ id that this API also has a job for is answered from the job (it knows characters, callback_url, error); otherwise you get the Studio article, with source: "studio". Anything that is neither an artl_... id nor a job id is a 400.
  • GET /v1/articles?source= chooses what a page holds: all (the default) is every Studio article plus the jobs Studio cannot have — one that never published, and the failed and deleted ones — newest first; api is only the jobs this API created; studio is only what Studio holds. Any other value is a 400 invalid_source.
  • starting_after takes either form and means “older than this article”, by publication time. ids may mix the two forms in one call; an artl_ id this key cannot see is skipped rather than failing the page.
  • A publication-scoped key sees only articles in its own publications, whatever created them. Another account’s article is a 404, never a 403.

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 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.

A regenerate starts from the settings the article was last narrated with; anything this request sends explicitly overrides them, under the same blank vs. omitted rule.

Update article

PATCH /v1/articles/{id} changes what an article says about itself — its title, summary, tags, cover, publish time and visibility — without re-narrating a word of it. It takes an article id (artl_...) or a job_id (ajob_...) and reaches any article in your Studio, whatever created it (see Any article, any origin). Only the fields you send change; omitting one, or sending null, leaves it alone.

Parameter Type Description
title string Headline. Cannot be blank; at most 500 characters.
summary string Description shown on the player. "" clears it.
authors string[] The complete byline; replaces the article’s authors (at most 20).
tags string[] Replaces the article’s tags (at most 50).
privacy string "public" or "unlisted".
published_at string ISO 8601 publish time.
art_url string Cover image URL.
metadata object Up to 8 string values (512 bytes total). Only on an article this API created — a 400 otherwise.

Unlisted and scheduled. privacy: "unlisted" keeps the player and the embed working but hides the article from playlists, feeds, roundups and listings. A published_at in the future schedules the article: unless you also send privacy, it is set to unlisted and flipped public at that time. A published_at in the past or present leaves privacy untouched.

The answer is the same article resource every other endpoint returns. An article that is still converting (queued / processing) has nothing published to edit yet and answers 409 job_not_complete; a deleted one answers 409 article_deleted.

curl -X PATCH https://api.everlit.audio/v1/articles/artl_9f3k... \
  -H "Authorization: Bearer $EVERLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"title":"City approves budget, 7-2","tags":["city hall","budget"],"privacy":"unlisted"}'
# => 200 { "object": "article", "id": "artl_9f3k...", "title": "City approves budget, 7-2", "privacy": "unlisted", ... }

Deleting articles

DELETE /v1/articles/{id} answers 204 and does one of two things. It takes an article id (artl_...) or a job_id (ajob_...), so it also takes down an article Studio, the widget, WordPress or RSS made — see Any article, any origin.

  • 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 (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
}

default_voice is the narrator English content gets; language_voices lists every language the publication configures a voice for. Everything else a publication hands its articles is in Publication settings.

Publication settings

GET /v1/publications/{id} returns one publication with everything its articles inherit, and PATCH /v1/publications/{id} changes it. These apply to every article the publication produces — the API, the Auto Audio widget on your site, RSS ingestion and Studio alike — so change them deliberately; to affect one article only, pass the audio fields to POST /v1/articles.

{
  "object": "publication",
  "id": "pblc_abc123",
  "name": "The Daily",
  "url": "https://example.com",
  "language_voices": ["en-US", "es-MX"],
  "default_voice": "109",
  "defaults": {
    "language": "en-US",
    "voices": {
      "en-US": { "voice": "109", "guest_voice": "115", "disclaimer": "This audio was generated by AI.", "disclaimer_voice": "112" },
      "es-MX": { "voice": "204", "guest_voice": null, "disclaimer": "Audio generado por IA.", "disclaimer_voice": null }
    },
    "author_voices": [{ "author": "Jane Doe", "voice": "118" }],
    "ui_disclaimers": { "en-US": "Narrated by AI." },
    "music": true,
    "intro_music": "34",
    "outro_music": "35",
    "intro_music_duration": 15,
    "outro_music_duration": 15,
    "intro_music_pad": 5,
    "outro_music_pad": 4,
    "audio_polish": true,
    "audio_polish_level": "2",
    "podcast_mode": false,
    "read_urls": false,
    "read_alt_text": false,
    "read_author": true,
    "player": {
      "size": "large", "cover_art": true, "title_intro": "Listen Now:", "title_icon": "headphones",
      "theme": {
        "colors": { "accent": "#1a2b3c", "background": "#ffffff" },
        "fonts": { "title": { "src": "https://example.com/fonts/tiempos.woff2", "size_adjust": 100 } }
      }
    },
    "studio": { "read_along": true },
    "sharing": { "enabled": true, "link": "canonical" },
    "email_capture": { "enabled": false, "label": null, "prompt": null },
    "pauses": { "title": 0.5, "subtitle": [0.4, 0.6], "byline": null, "disclaimer": null, "header_to_body": null },
    "extraction": {
      "content_selector": ".article-body",
      "title_selector": null,
      "date_selector": null,
      "exclude_selectors": [".share-widget", ".newsletter-signup"],
      "tag_field_map": { "section": "category" },
      "site_name_tag_prefix": null,
      "tag_namespaces": ["author", "category", "language"]
    },
    "widget": {
      "allowed_origins": ["example.com"],
      "click_to_create": false,
      "click_to_create_message": null,
      "generate_on_first_view": false,
      "rss_only": false
    },
    "paywall": {
      "enabled": false,
      "access_attribute": "everlit-access",
      "cta_text": null,
      "cta_url": null,
      "button_label": null,
      "button_color": null,
      "button_color_enabled": false
    }
  }
}

PATCH takes any subset, follows the same blank vs. omitted rule, and returns the updated publication in the shape above. Unknown fields are a 400 unknown_parameter at any depth (player.colour, voices.en-US.accent).

Voices

Voices, spoken disclaimers and the voice that reads them are configured per language, because that is how narration is chosen: we detect the article’s language (or take the language you send) and use that language’s entry.

Field Type Description
voices object Keyed by language tag (en-US, es-MX, es, or the catch-all default). Each value is { voice, guest_voice, disclaimer, disclaimer_voice }.
voices.{lang}.voice string Narrator voice id for this language. Cannot be blank.
voices.{lang}.guest_voice string The second voice in podcast_mode for this language. Configured exactly like voice, and like it cannot be blank: leaving it unset means a voice is chosen when a dialogue is actually produced, which is not the same as “no guest”.
voices.{lang}.disclaimer string Spoken AI disclaimer. "" narrates none for this language.
voices.{lang}.disclaimer_voice string Voice that reads it. "" only while that language’s disclaimer is blank too.
language string The tag to narrate in without detecting, e.g. "en-US". Must be a key of voices; "" goes back to detecting per article.
author_voices array [{ "author": "Jane Doe", "voice": "118" }] — a byline’s own voice, which beats the language voice. Author names match ignoring case.
ui_disclaimers object Per language: the AI disclosure line shown under the player (the spoken one is voices.{lang}.disclaimer). "" shows none for that language; null drops the language.

A voices or ui_disclaimers patch merges language by language: languages you do not mention are untouched, and within a language, fields you do not mention are untouched. Send "es-MX": null to remove that language entirely (voice, disclaimer and disclaimer voice together). author_voices is the exception — it is a list, so sending it replaces the whole list, and [] clears it.

Language tags are canonicalized (es-mxes-MX), the same normalization the Auto Audio widget applies, so both surfaces read each other’s settings.

Two notes on older publications. A publication configured before per-language voices existed reports its single voice, guest voice and disclaimer under en-US — where narration actually falls back to them, and where they migrate to. And a language whose voice is a legacy rotation (several ids we pick from) reports the first id; writing voice replaces the rotation with the one voice you send.

Audio

Field Type Description
music boolean Shorthand for turning both intro_music and outro_music off (false) or back on (true). It is derived, not stored: reading it is “at least one bed plays”, and writing it leaves the bed ids alone, so music: true puts the same beds back. Sending it in the same request as an intro_music / outro_music that disagrees is a 400.
intro_music / outro_music string The bed every article gets: a mixable id, "shuffle" for a random track from your intro/outro music, or "" for none. Setting an id clears a shuffle, and a shuffle keeps the id, so turning the shuffle off puts the same bed back.
intro_music_duration / outro_music_duration number Seconds of bed before / after the narration.
intro_music_pad / outro_music_pad number Seconds the bed overlaps the narration.
audio_polish boolean Rewrite the text for the ear before narrating.
audio_polish_level string How far it may rewrite: "0" (symbols and numbers only), "1", "2", "3" (free rewrite). Default "2".
podcast_mode boolean Two-voice conversational narration, using guest_voice.
read_urls / read_alt_text boolean Read URLs / image alt text aloud.
read_author boolean Read the byline aloud.
pauses object Silence after title, subtitle, byline, disclaimer, header_to_body. Each is seconds as a number, or [min, max] for a range we pick from per article.

Music is always mixed at our end: the player is handed one finished, pre-mixed file, so the only music switch is which beds play, not whether we mix.

Music beds

GET /v1/mixables lists the intro/outro beds intro_music and outro_music (here and on POST /v1/articles) may reference — Everlit’s library plus anything assigned to your publisher — so you never have to guess an id and read the 404 back:

{
  "object": "list",
  "data": [
    { "id": "42", "name": "Calm Intro", "kind": "intro", "tags": { "mood": "calm" },
      "owned_by": "everlit", "preview_url": "https://cdn.everlit.audio/mixables/calm-intro.mp3" }
  ],
  "has_more": false
}
Field Description
id Pass this as intro_music / outro_music.
name Display name (the filename without its extension).
kind As stored on the bed (intro, outro, …).
tags Free-form tags on the bed (mood, genre, …).
owned_by everlit (global library) or publisher (assigned to your account only).
preview_url A clip you can hand a person, or null.

Query params: kind (filter, e.g. ?kind=intro), q (free-text on name or tags), limit, starting_after. No TTS entitlement is required — it is plan-based like the rest of the Articles API.

Your own beds. POST /v1/mixables adds a track to your account: we fetch the audio from url and store it, and the bed it returns can be used as intro_music / outro_music straight away. Everlit’s own library is never changed — owned_by: "everlit" rows are read-only for every account.

Field Type Required Description
name string yes Display name, 1–120 characters. Becomes the bed’s filename with the fetched file’s extension.
url string yes https:// URL of the audio file. Fetched by our servers: mp3, wav, m4a, ogg or flac, at most 25 MB, no IP-literal or private hosts, at most 3 redirects.
kind string no intro-outro (a music bed — the default), sfx (a sound effect) or ambient.
tags object no tempo, subjects and attributes, each an array of up to 20 strings of at most 40 characters. Any other key is a 400.
static_duration boolean no true when the track must play in full instead of being trimmed to the configured seconds.
publications string[] no Publication ids (pblc_...) this bed is limited to. Omit (or []) to make it usable across the whole account.

The response is the same row the list returns, plus a Location header. The bed’s Studio dropdown label is set for you (tags.display_kind: Music for intro-outro, Sound Effects for sfx, Ambient for ambient).

GET /v1/mixables/{id} reads one bed — yours or one of Everlit’s. DELETE /v1/mixables/{id} deletes a bed you uploaded and clears it from any publication still pointing at it (204). An Everlit library track is a 403 not_owned; a bed on another account is a 404, like any id you cannot see.

curl -X POST https://api.everlit.audio/v1/mixables \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Upbeat Intro",
    "url": "https://cdn.example.com/beds/upbeat-intro.mp3",
    "kind": "intro-outro",
    "tags": { "tempo": ["upbeat"], "subjects": ["news"], "attributes": ["energetic"] }
  }'

Player and page

Field Type Description
player.size string large or small.
player.cover_art boolean Show the article’s cover art in the player.
player.title_intro string Line above the title, e.g. "Listen Now:". "" shows none.
player.title_icon string Icon beside the title ("headphones"). "" shows none.
studio.read_along boolean Highlight words during preview playback in Studio.
sharing.enabled boolean Show the share button on the player.
sharing.link string Which URL it shares: canonical, canonical_hosted or hosted.
email_capture.enabled boolean Ask listeners for an email address.
email_capture.label string Button label, at most 30 characters.
email_capture.prompt string Prompt text, at most 120 characters.

Theme

player.theme is the player’s design — the same colours and fonts Studio’s Player design screen paints the embed with.

Field Type Description
player.theme.colors object Named colours, each a hex string like "#1a2b3c". The names: background, accent, primary, title_text, body_text, border, cover_background, control_background, control_icon, progress_bar, progress_bar_active, progress_handle, playlist_tracks_background, playlist_tracks_border.
player.theme.fonts object title and body, each { "src": "https://…/font.woff2", "size_adjust": 100 }. src must be an https URL ending in .woff2, .woff, .ttf or .otf; size_adjust is a CSS size-adjust percentage between 50 and 200 (default 100).

The theme merges key by key, unlike the lists elsewhere in this API: a colour or font you do not mention is left exactly as it was. Send "" for one colour, or "" in place of a whole font object, to put that one back to its derived default. Colours that are not #rrggbb are a 400, and a colour set directly on the publication outside these fourteen names is preserved.

Page extraction

How the Auto Audio widget reads a page. Everything here is an override: leave it unset and articles are extracted automatically, which is right for nearly every site. Reach for it when a page’s furniture is being narrated, or the wrong element is picked as the body.

Field Type Description
extraction.content_selector string CSS selector for the article body when automatic extraction picks the wrong element. Every match is read, in page order. "" goes back to automatic extraction.
extraction.title_selector string CSS selector for the headline, when the page has no usable title metadata.
extraction.date_selector string CSS selector for the publication date, same.
extraction.exclude_selectors array of strings Page furniture never to narrate — share widgets, newsletter sign-ups, related-story rails. At most 100 entries, each at most 500 characters.
extraction.tag_field_map object Page field name → tag prefix, e.g. {"section": "category"}: which of the page’s own metadata fields become article tags, and how each is prefixed. At most 50 entries.
extraction.site_name_tag_prefix string When set (e.g. "site"), every article gets a "<prefix>:<site name>" tag taken from the page. "" adds none.
extraction.tag_namespaces array of strings Tag namespaces the public playlist tag picker offers: any of author, category, language, content. Anything else is a 400.

The three lists and the map replace what is stored rather than merging into it, so send the whole list you want. [] (or {}) clears one: no exclusions, no tag fields, no namespaces offered.

Widget behaviour

When and where the Auto Audio widget narrates.

Field Type Description
widget.allowed_origins array of strings Hostnames the widget may narrate pages from, e.g. ["example.com", "www.example.com"]. Entries are hostnames only — a scheme or a path is a 400. [] allows every page the widget runs on. Replaces the whole list.
widget.click_to_create boolean Wait for a reader to press play before narrating a page that has no audio yet, instead of narrating it on the first view.
widget.click_to_create_message string Message shown while that first narration is being made. "" shows the default.
widget.generate_on_first_view boolean Narrate an older article on its first pageview instead of waiting for it to draw repeat traffic. For back catalogues.
widget.rss_only boolean Only narrate articles that arrive through an RSS feed; the widget narrates nothing on its own.

Paywall

Gate the player behind your paywall. Readers without access hear nothing and see the call to action instead.

Field Type Description
paywall.enabled boolean Turn the gate on.
paywall.access_attribute string The HTML attribute your page sets on a subscriber to say they have access. Defaults to everlit-access.
paywall.cta_text string The line shown to a reader without access, e.g. "Subscribe today to listen to this content".
paywall.cta_url string Where the button sends them — an http(s) URL. "" removes the link.
paywall.button_label string Label on that button, e.g. "Get Access".
paywall.button_color string Button colour as #rrggbb. Used only when button_color_enabled is true.
paywall.button_color_enabled boolean Use button_color instead of the player’s own colours.

Every string field here follows the usual rule: omit it (or send null) to leave it alone, and send "" to clear it.

curl -X PATCH https://api.everlit.audio/v1/publications/pblc_abc123 \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "voices": { "es-MX": { "voice": "204", "disclaimer": "Audio generado por IA." } },
        "audio_polish_level": "3",
        "player": { "title_intro": "Listen:" }
      }'

Playlists

A playlist is a curated group of your published articles: pinned tracks in order, optionally topped up by a tag rule (“fill” — any newly published article carrying one of the playlist’s tags is added, newest first), with a player embed and its own RSS feed for podcast platforms. Studio owns the playlist itself (it is where you build and manage one); this API is for reading a playlist’s contents and pinning or unpinning tracks as new audio publishes — most useful right after create_article succeeds.

Method Path Purpose
GET /v1/playlists List this account’s playlists
POST /v1/playlists Create a playlist
GET /v1/playlists/{id} One playlist, with its resolved tracks and pins
PATCH /v1/playlists/{id} Change its rules
DELETE /v1/playlists/{id} Delete it
POST /v1/playlists/{id}/articles Pin an article
PATCH /v1/playlists/{id}/articles/{article} Move a pinned article
DELETE /v1/playlists/{id}/articles/{article} Unpin an article
curl -X POST https://api.everlit.audio/v1/playlists/plist_a1b2c3/articles \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"article": "artl_9f3k2q9m7v4n6b1c5d8e0h"}'
# => 201
# {"object":"playlist_pin","playlist":"plist_a1b2c3","id":"plart_9k2m","article_id":"artl_9f3k...","position":4}

article (on both the pin and unpin routes) accepts either an ajob_... job_id from create_article — resolved to the article it published, so you never have to wait for a second lookup — or the artl_... article id directly. A job with no published article yet answers 409 job_not_complete. Unpinning only removes a pin: an article the tag rule filled in is not a pin and cannot be removed this way (edit the rule in Studio instead).

The playlist resource:

{
  "object": "playlist",
  "id": "plist_a1b2c3",
  "name": "Morning Briefing",
  "description": "Top stories, read aloud",
  "tags": ["news"],
  "ignore_tags": [],
  "match": "any",
  "size": 25,
  "order": "latest",
  "within": "any",
  "within_start": null,
  "within_end": null,
  "fill": true,
  "publications": [],
  "embed_url": "https://everlit.audio/embeds/plist_a1b2c3",
  "embed_html": "<iframe src=\"https://everlit.audio/embeds/plist_a1b2c3\" ...></iframe>",
  "feed_url": "https://creator.everlit.audio/feeds/lists/plist_a1b2c3",
  "feed_distributed": false
}

publications, when non-empty, restricts the playlist to those publications’ articles and to keys scoped to (at least) one of them — an unrestricted playlist ([]) is visible to every key on the account. feed_url is always present — it is the RSS feed creator itself serves for the playlist, valid whether or not you have submitted it anywhere; feed_distributed tells you whether it has actually been submitted to podcast platforms (Apple, Spotify, …) yet. GET /v1/playlists/{id} additionally returns tracks (the resolved, ordered list of articles — pinned plus any the tag rule filled in, each with article_id, title, published_at, duration_seconds, publication, player_url) and pins (just the pins, with their id and position, for the ones remove_from_playlist / DELETE .../articles/{article} can remove).

Changing the rules

PATCH /v1/playlists/{id} changes the playlist itself. Only the fields you send change; omitting one (or sending null) leaves it alone. Send at least one field.

Field Type Meaning
name string Playlist name
description string Shown on the player and in the feed
tags array of strings The fill rule: articles carrying these tags join automatically
match "any" | "all" Whether an article needs any of the tags or all of them
ignore_tags array of strings Exclude articles carrying any of these tags
size integer 1–100 How many tracks the playlist holds
order played | viewed | latest | oldest | random Which tracks survive the cap, and in what order
within any | current_day | current_week | current_month | last_day | last_week | last_month | custom Only articles published in this window
within_start, within_end string Required when within is "custom"
publications array of pblc_ ids Restrict the playlist to these publications
fill boolean false: pinned articles only, no tag rule
curl -X PATCH https://api.everlit.audio/v1/playlists/plist_a1b2c3 \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tags": ["news", "politics"], "ignore_tags": ["sponsored"], "size": 15, "order": "latest"}'

An unknown field is a 400 unknown_parameter; a publication your key cannot see is a 404 publication_not_found.

Reordering and deleting

PATCH /v1/playlists/{id}/articles/{article} moves a pinned article. Send exactly one of:

  • before — the article (an artl_... id or an ajob_... job_id) this one should sit in front of, or null to move it to the end.
  • position — a 1-based slot among the playlist’s pins. A position past the last pin moves it to the end.

Sending both, or neither, is a 400. Only pins can be moved — an article the tag rule filled in is ordered by the rule, and moving one answers 404 article_not_found. The response is the pin in its new place: {"object":"playlist_pin","playlist":"plist_a1b2c3","id":"plart_9k2m","article_id":"artl_9f3k...","position":1}.

DELETE /v1/playlists/{id} deletes the playlist itself and answers 204: its player and page come down, but the articles in it are untouched and keep their own players. If the playlist had a podcast feed, the feed is deactivated rather than destroyed — it stops publishing new episodes, and its metadata is still there if you rebuild the show later.

Podcast feeds

Every playlist is already served as an RSS feed at its feed_url. What turns that URL into a podcast Apple, Spotify or Overcast will accept is the show’s metadata — title, description, owner email, category, cover art, language — plus switching distribution on. That is what these routes do.

The flow: PATCH .../feed to fill the metadata → POST .../feed/activate → submit the feed_url to the directories. Submission itself is done by a person in Studio → Feeds (the directories require a human to accept their terms); everything up to that point is here.

Method Path Purpose
GET /v1/playlists/{id}/feed The feed, its metadata and its directory status
PATCH /v1/playlists/{id}/feed Set the podcast metadata
POST /v1/playlists/{id}/feed/activate Validate and start publishing
POST /v1/playlists/{id}/feed/deactivate Stop publishing (metadata kept)
GET /v1/playlists/{id}/feed/platforms Where the show stands on each directory
POST /v1/playlists/{id}/feed/platforms/{platform}/submitted Record a submission the user made
POST /v1/playlists/{id}/feed/platforms/{platform}/live Record the directory listing the show
DELETE /v1/playlists/{id}/feed/platforms/{platform} Clear one directory’s record
POST /v1/playlists/{id}/feed/ping Ping Overcast to re-crawl
curl -X PATCH https://api.everlit.audio/v1/playlists/plist_a1b2c3/feed \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "title": "The Morning Briefing",
        "description": "Top stories from the newsroom, read aloud every weekday.",
        "owner_email": "podcasts@example.com",
        "category": "News",
        "artwork_url": "https://cdn.example.com/briefing-3000.jpg",
        "language": "en",
        "keywords": ["news", "local"]
      }'

curl -X POST https://api.everlit.audio/v1/playlists/plist_a1b2c3/feed/activate \
  -H "Authorization: Bearer $EVERLIT_API_KEY"

Required before a feed can go live: title, description, owner_email, category, artwork_url and language. Activation fills what it can from the playlist itself (its name becomes the title, its description the description, its tags the keywords, and the owner email defaults to a managed Everlit address that receives the directories’ verification codes), so in practice only artwork_url usually has to be set by hand — square JPEG or PNG, 1400–3000px.

A feed never lacks a description: when none was written, one is derived as “A show from publication featuring title” (the playlist’s publication when it is restricted to one, otherwise the account name).

activate answers 200 with active: true once the feed is live. When metadata is still missing it is a 400 feed_incomplete whose error.message and error.missing_fields name the fields (usually just artwork_url) and whose error.feed is the feed as it stands; set them with PATCH .../feed and call activate again.

Every writable field, all optional: title (≤255), description (≤4000), subtitle (≤255), summary (≤4000), artwork_url (http(s)), website_url (http(s)), author (≤255), owner_name (≤255), owner_email (an email address), category, subcategory, language (≤10, e.g. "en"), explicit (boolean), keywords (array of strings), copyright (≤255), type ("episodic" or "serial"), complete (boolean — the show is finished), locked (boolean — block other platforms from importing the feed), funding_url (http(s)) and funding_text (≤255). null leaves a field alone; "" clears it.

The feed resource:

{
  "object": "feed",
  "playlist": "plist_a1b2c3",
  "feed_url": "https://creator.everlit.audio/feeds/lists/plist_a1b2c3",
  "active": true,
  "distribution_enabled": true,
  "validation_status": "valid",
  "validation_errors": [],
  "title": "The Morning Briefing",
  "description": "Top stories from the newsroom, read aloud every weekday.",
  "subtitle": null,
  "summary": null,
  "artwork_url": "https://cdn.example.com/briefing-3000.jpg",
  "website_url": null,
  "author": "Morning Briefing",
  "owner_name": "Example News",
  "owner_email": "podcasts@example.com",
  "category": "News",
  "subcategory": null,
  "language": "en",
  "explicit": false,
  "keywords": ["news", "local"],
  "copyright": null,
  "type": "episodic",
  "complete": false,
  "locked": true,
  "funding_url": null,
  "funding_text": null,
  "podcast_guid": "ead4c236-bf58-58c6-a2c6-a6b28d128cb6",
  "last_validated_at": "2026-09-11T09:00:00Z",
  "distributions": [
    { "platform": "apple", "status": "live", "platform_url": "https://podcasts.apple.com/...", "updated_at": "2026-09-11T09:05:00Z" }
  ]
}

active is the one to check: it means the feed both validates and has distribution on. distribution_enabled is the switch alone, validation_status (pending / valid / incomplete) the metadata alone. podcast_guid is the show’s permanent identity in the podcast namespace — it never changes once the feed exists, which is what lets a show move hosts without losing its subscribers. distributions lists the directories the show has been submitted to and where each one stands.

A playlist with no feed set up yet still answers GET .../feed: feed_url is there (it is always served), active is false and every metadata field is null.

Directories

A feed is only a URL until a directory carries it. Every directory except Overcast requires a person to sign in and submit the feed — they all make someone accept their terms — so these routes record what happened rather than do it. The flow:

  1. POST .../feed/activate, so there is a feed worth submitting.
  2. GET .../feed/platforms and hand the user the submission_url for the directory they want.
  3. The user submits the feed_url there.
  4. POST .../feed/platforms/{platform}/submitted — with notes if there is anything worth remembering.
  5. When the directory approves the show (hours to days), POST .../feed/platforms/{platform}/live with platform_url, the show’s page on that directory.

The six platforms are apple, spotify, pocketcasts, amazon, youtube and overcast. status is one of not_started (nothing recorded yet), pending, submitted, live, failed or removed.

Two of them have no submission_url. Apple is submitted in Apple Podcasts Connect — the Studio wizard walks a user through signing in there with an Apple ID and pasting the feed URL. Overcast takes no submission at all: it discovers feeds on its own and accepts a ping. POST .../feed/ping tells Overcast the feed changed so it re-crawls now; every other directory polls on its own schedule, and there is nothing to ping.

# The user submitted the feed at podcasters.spotify.com
curl -X POST https://api.everlit.audio/v1/playlists/plist_a1b2c3/feed/platforms/spotify/submitted \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Submitted from the newsroom account"}'

# Spotify approved it
curl -X POST https://api.everlit.audio/v1/playlists/plist_a1b2c3/feed/platforms/spotify/live \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"platform_url": "https://open.spotify.com/show/4kL2..."}'

Both take an optional platform_podcast_id (the directory’s own id for the show); submitted also takes notes (≤1000 characters). platform_url is required on live for every directory a person submits to — that is, all but Overcast. Both need the feed to be active first, or they answer 409 feed_not_active; submitted on a platform already live is a 409 already_live (reset it first). An unknown platform is a 404 platform_not_found.

DELETE .../feed/platforms/{platform} forgets Everlit’s record — the platform reads not_started again — and 404 platform_not_started when there was nothing to forget. It withdraws nothing at the directory itself.

The feed_platform resource:

{
  "object": "feed_platform",
  "playlist": "plist_a1b2c3",
  "platform": "spotify",
  "name": "Spotify",
  "status": "live",
  "submission_url": "https://podcasters.spotify.com/",
  "manual_submission": true,
  "supports_ping": false,
  "platform_url": "https://open.spotify.com/show/4kL2...",
  "platform_podcast_id": "4kL2...",
  "submitted_at": "2026-09-11T09:05:00Z",
  "live_at": "2026-09-13T14:20:00Z",
  "last_checked_at": "2026-09-13T14:20:00Z",
  "last_error": null,
  "notes": "Submitted from the newsroom account"
}

GET .../feed/platforms answers { "object": "list", "data": [...], "has_more": false } with one row per platform, in that order, whether or not anything has been recorded for it. The distributions array on the feed itself carries the same fields (minus object and playlist, plus updated_at) but only for the platforms that have a record.

The ping answers:

{
  "object": "feed_ping",
  "playlist": "plist_a1b2c3",
  "platform": "overcast",
  "pinged_at": "2026-09-13T14:20:00Z",
  "ok": true,
  "message": "Overcast will re-crawl the feed shortly."
}

ok: false with a message means Overcast refused or timed out; it is safe to try again. Pinging a feed that is not active is a 409 feed_not_active.

Ingestion feeds

An ingestion feed is a standing instruction rather than a one-off call: point a publication at an RSS or Atom feed and every item the feed publishes from then on is narrated automatically, with that publication’s defaults — voice, music, disclaimer, player. Set it up once and the newsroom’s own CMS feed becomes the input to the audio.

Everything about how an item is narrated comes from the publication, not the feed. The feed itself only decides which items are picked up: how often it is checked, how many a single check may take, and how far back to go.

Each item narrated is an article and counts against your plan’s monthly article quota, exactly as if you had called POST /v1/articles for it. A busy feed with no max_items can spend a month’s quota in one poll; max_items and oldest_date are what keep the first poll from narrating the whole archive.

Method Path Purpose
GET /v1/ingestion-feeds Feeds this key can see (optionally ?publication=pblc_...)
POST /v1/ingestion-feeds Start ingesting a feed → 201
GET /v1/ingestion-feeds/{id} One feed
PATCH /v1/ingestion-feeds/{id} Change the interval, caps, name, or pause/resume it
DELETE /v1/ingestion-feeds/{id} Stop ingesting and delete the feed → 204
POST /v1/ingestion-feeds/{id}/poll Check the feed now
GET /v1/ingestion-feeds/{id}/items What the feed has seen and what became of it
curl -X POST https://api.everlit.audio/v1/ingestion-feeds \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "publication": "pblc_a1b2c3",
        "url": "https://example.com/feed.xml",
        "name": "Front page",
        "poll_interval_minutes": 15,
        "max_items": 5,
        "oldest_date": "2026-09-01T00:00:00Z"
      }'

publication and url are required; everything else is optional. poll_interval_minutes is an integer from 5 to 1440 (default 15), max_items an integer from 1 to 500 (null = no cap), oldest_date an ISO 8601 timestamp below which items are skipped (null = the whole feed), name a label of up to 120 characters, and status either "active" (the default) or "paused". poll_now (default true) polls once immediately as well as on the interval, so you see whether the feed parses without waiting a quarter of an hour.

A feed URL is ingested once across Everlit: a url already on a feed answers 409 ingestion_feed_exists, naming the existing feed’s id when it is one of yours. The url of an existing feed cannot be changed — a different address is a different feed, so delete this one and create another.

Deleting a feed removes it and the record of the items it has seen. Articles already narrated from it are not deleted — they are ordinary articles with their own players from the moment they are published.

The feed resource:

{
  "object": "ingestion_feed",
  "id": "rsif_9k2mq4x7dhz",
  "publication": "pblc_a1b2c3",
  "url": "https://example.com/feed.xml",
  "name": "Front page",
  "status": "active",
  "poll_interval_minutes": 15,
  "max_items": 5,
  "oldest_date": "2026-09-01T00:00:00Z",
  "last_polled_at": "2026-09-11T09:15:00Z",
  "last_build_date": "2026-09-11T09:12:00Z",
  "last_error": null,
  "items": { "total": 42, "completed": 40, "processing": 1, "error": 1 },
  "created_at": "2026-09-01T12:00:00Z",
  "updated_at": "2026-09-11T09:15:00Z"
}

status is active while the feed is being polled, paused while you have stopped it, and error when the last poll failed — last_error says why, and setting status back to "active" clears it. A paused feed is polled by nobody: POST .../poll on one answers 409 ingestion_feed_paused.

Polling now

POST /v1/ingestion-feeds/{id}/poll queues an immediate check and answers at once — the items are narrated in the background, so read GET .../items afterwards to see what came of it:

{ "object": "ingestion_poll", "feed": "rsif_9k2mq4x7dhz", "queued": true, "queued_at": "2026-09-11T09:20:00Z" }

Manual polls are throttled to one a minute per feed; inside that minute the call is a 429 poll_too_soon with a Retry-After header giving the seconds left. The interval polls are unaffected.

Items

GET /v1/ingestion-feeds/{id}/items lists what the feed has seen, newest first. ?limit is 1 to 100 (default 50), ?status filters to pending / processing / completed / error, and ?starting_after takes an item id to page after; has_more says whether another page exists.

{
  "object": "ingestion_item",
  "id": "rsii_4t8bn1qwlvz",
  "feed": "rsif_9k2mq4x7dhz",
  "guid": "https://example.com/news/city-budget",
  "url": "https://example.com/news/city-budget",
  "title": "City approves budget",
  "status": "completed",
  "article": "artl_x7y8z9",
  "published_at": "2026-09-11T09:05:00Z",
  "last_error": null,
  "created_at": "2026-09-11T09:15:00Z",
  "updated_at": "2026-09-11T09:16:30Z"
}

article is the article the item became — an artl_ id you can pass to GET /v1/articles/{id}, PATCH, or POST /v1/playlists/{id}/articles — and is null until the narration finishes. An item that failed carries status: "error" and the reason in last_error; the feed keeps going.

Pronunciations

The pronunciation library fixes how a name or word is spoken, for every future narration — not just one article. A rule is either account-wide (publication omitted or null, applied to every publication) or scoped to one publication (its own rules apply on top of the account-wide ones). This changes only narrations made after the rule is set; an article already narrated keeps its original audio until you regenerate: true it.

Method Path Purpose
GET /v1/pronunciations List this account’s rules (optionally ?publication=pblc_...)
PUT /v1/pronunciations Add or update a rule (upsert on word + scope)
DELETE /v1/pronunciations/{word} Remove a rule (optionally ?publication=pblc_...)
curl -X PUT https://api.everlit.audio/v1/pronunciations \
  -H "Authorization: Bearer $EVERLIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"word": "Nguyen", "say": "win"}'
# => 200
# {"object":"pronunciation","word":"Nguyen","say":"win","exact_match":false,"publication":null,"updated_at":"2026-09-09T00:00:00Z"}

word (up to 120 characters) is the text as it appears in your content; say (up to 300 characters) is how it should sound — spell it phonetically, the way you’d want it read aloud, not an IPA transcription. exact_match (default false) matches the word only as a whole word rather than also inside longer words containing it. A PUT with the same word and publication updates the existing rule in place rather than creating a second one; a word set both account-wide and for a publication keeps both — the publication’s rule is what that publication’s narrations use.

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.
  • Concurrent conversions are capped at the number of publications on your account: one running conversion per publication (max_concurrent_articles under plan in GET /v1/account; null means no cap). Over it, POST /v1/articles answers 429 concurrency_limit_exceeded with Retry-After; wait for a running job to finish, then retry. Requests that converge onto an existing article never take a slot. This cap is a plan feature only: pay-as-you-go credit or a card adds extra lanes for speech (POST /v1/speech), never for article conversions.
  • 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. PATCH /v1/publications/:id is a write: it draws on the create bucket, not the read one.
  • 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
get_publication GET /v1/publications/{id}
update_publication PATCH /v1/publications/{id}
create_article POST /v1/articles (accepts an idempotency_key argument)
get_article GET /v1/articles/{id} (accepts idempotency_key instead of id)
list_articles GET /v1/articles (accepts ids and idempotency_key)
update_article PATCH /v1/articles/{id}
delete_article DELETE /v1/articles/{id}
list_voices GET /v1/voices
list_mixables GET /v1/mixables
update_playlist PATCH /v1/playlists/{id}
delete_playlist DELETE /v1/playlists/{id}
move_in_playlist PATCH /v1/playlists/{id}/articles/{article}
get_feed GET /v1/playlists/{id}/feed
update_feed PATCH /v1/playlists/{id}/feed
activate_feed POST /v1/playlists/{id}/feed/activate
deactivate_feed POST /v1/playlists/{id}/feed/deactivate
list_feed_platforms GET /v1/playlists/{id}/feed/platforms
mark_feed_submitted POST /v1/playlists/{id}/feed/platforms/{platform}/submitted
mark_feed_live POST /v1/playlists/{id}/feed/platforms/{platform}/live
reset_feed_platform DELETE /v1/playlists/{id}/feed/platforms/{platform}
ping_feed POST /v1/playlists/{id}/feed/ping
list_ingestion_feeds GET /v1/ingestion-feeds
create_ingestion_feed POST /v1/ingestion-feeds
get_ingestion_feed GET /v1/ingestion-feeds/{id}
update_ingestion_feed PATCH /v1/ingestion-feeds/{id}
delete_ingestion_feed DELETE /v1/ingestion-feeds/{id}
poll_ingestion_feed POST /v1/ingestion-feeds/{id}/poll
list_ingestion_items GET /v1/ingestion-feeds/{id}/items
create_mixable POST /v1/mixables
delete_mixable DELETE /v1/mixables/{id}

Endpoint: https://api.everlit.audio/v1/mcp — Streamable HTTP transport, stateless, JSON responses (see Sessions and updates). Authenticate either by connecting with OAuth (log in to Studio, no key to copy) or with the same Authorization: Bearer api_... header the REST endpoints take — see Connect with OAuth.

{
  "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 an audio article from a url, or from title + text, in a publication; returns the queued job with job_id. Poll get_article until “succeeded”, then use player_url.

Argument Type Required Description
publication string yes The publication id (pblc_…) from list_publications.
url string or null no Public http(s) URL to fetch and narrate; required unless title and text are given.
title string or null no Article headline (with text, skips fetching the url).
text string or null no Plain-text article body, up to 200,000 characters; same inline tags as create_speech.
audio_url string or null no https URL of your own MP3/WAV to publish instead of narrating; title required.
authors array or null no Author names, read aloud as the byline.
summary string or null no Short description shown on the player.
category string or null no Section/category name.
tags array or null no Extra tags.
art_url string or null no Cover image URL.
published_at string or null no ISO 8601 publication time; defaults to now.
voice string or integer or null no Narrator voice id from list_voices.
guest_voice string or integer or null no Second voice, used when podcast_mode is on.
language string or null no Language tag to narrate in (e.g. “en-US”), skipping detection.
podcast_mode boolean or null no Two-voice conversational narration.
music boolean or null no Mix the publication’s intro/outro beds in; default true.
intro_music string or null no Intro bed: a music bed id, “shuffle” for a random one, or “” for none.
outro_music string or null no Outro bed: a music bed id, “shuffle” for a random one, or “” for none.
intro_music_duration number or null no Seconds of intro bed before the narration.
outro_music_duration number or null no Seconds of outro bed after the narration.
intro_music_pad number or null no Seconds the intro bed overlaps the narration.
outro_music_pad number or null no Seconds the outro bed overlaps the narration.
audio_polish boolean or null no Rewrite the text for the ear before narrating.
audio_polish_level string or integer or null no How far Audio Polish may rewrite: “0” (symbols only) to “3”. Default “2”.
read_urls boolean or null no Read URLs aloud.
read_alt_text boolean or null no Read image alt text aloud.
read_author boolean or null no Read the byline aloud.
custom_byline string or null no Exact byline text to read instead of the generated one.
disclaimer string or null no Spoken AI disclaimer. “” narrates none; null uses the publication default.
disclaimer_voice string or integer or null no Voice that reads the spoken disclaimer.
ui_disclaimer boolean or null no Show the AI disclosure line under the player.
regenerate boolean or null no Re-narrate even if an article for this url exists, keeping its last settings unless overridden here.
callback_url string or null no https URL to POST the finished article to.
metadata object or null no Up to 8 string values, 512 bytes total.
idempotency_key string or null no Any unique string; a resubmit returns the original article job.
mix boolean or null no Alias of music.
intro_mixable string or null no Alias of intro_music.
outro_mixable string or null no Alias of outro_music.
sonic_optimizer boolean or null no Alias of audio_polish.
conversation_mode boolean or null no Alias of podcast_mode.

get_article

Fetch an article by its artl_ id, or by the job_id create_article returned while the id is still null. Returns its status, and player_url once succeeded.

Argument Type Required Description
id string no The article id (artl_…); any article in this Studio, whatever created it.
job_id string no The conversion job id (ajob_…); use it while id is still null.
idempotency_key string no Instead of id: the idempotency_key the article was created with.

list_articles

List this account’s articles, newest first - API jobs plus everything in Studio (widget, WordPress, RSS, uploads). Page with starting_after.

Argument Type Required Description
limit integer no Page size (default 20).
starting_after string no Return articles older than this id (artl_… or a job_id).
status enum: queued, processing, succeeded, failed, deleted no Filter by status.
publication string no Filter by publication id.
source enum: all, api, studio no all (default), api (jobs from this API) or studio (Studio only).
q string no Match on id, job id, url, title, error code or metadata.
ids array no Only these article or job ids, up to 50.

update_article

Change an article’s title, summary, authors, tags, cover image, publish time or visibility without re-narrating. privacy “unlisted” hides it from playlists and feeds.

Argument Type Required Description
id string yes The article id (artl_…) or job_id (ajob_…).
title string or null no Article headline.
summary string or null no Short description shown on the player.
authors array or null no Replaces the article’s byline.
tags array or null no Replaces the article’s tags.
privacy enum: public, unlisted, no “unlisted” hides it from playlists, feeds and lists; the player keeps working.
published_at string or null no ISO 8601 publish time; a future time schedules the article.
art_url string or null no Cover image URL.
metadata object or null no Up to 8 string values, 512 bytes total; API-created articles only.

delete_article

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

Argument Type Required Description
id string yes The article id (artl_…) or a job_id (ajob_…).

list_publications

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

No arguments.

get_publication

One publication and the defaults every article in it inherits: voice and disclaimer per language, per-author voices, music beds, Audio Polish and the player.

Argument Type Required Description
id string yes The publication id (pblc_…) from list_publications.

update_publication

Change a publication’s defaults; only the fields you send change, and voices / ui_disclaimers merge language by language. To change one article only, use create_article.

Argument Type Required Description
id string yes The publication id (pblc_…).
language string or null no Language tag to narrate in, skipping detection; one of the voices keys. “” turns it off.
music boolean or null no Play the beds; shorthand for turning intro_music and outro_music off or on.
intro_music string or null no Intro bed: a track id, “shuffle” for a random one, or “” for none.
outro_music string or null no Outro bed: a track id, “shuffle” for a random one, or “” for none.
intro_music_duration number or null no Seconds of intro bed before the narration.
outro_music_duration number or null no Seconds of outro bed after the narration.
intro_music_pad number or null no Seconds the intro bed overlaps the narration.
outro_music_pad number or null no Seconds the outro bed overlaps the narration.
audio_polish boolean or null no Rewrite the text for the ear before narrating.
audio_polish_level string or integer or null no How far Audio Polish may rewrite. One of 0, 1, 2, 3.
podcast_mode boolean or null no Two-voice conversational narration.
read_urls boolean or null no Read URLs aloud.
read_alt_text boolean or null no Read image alt text aloud.
read_author boolean or null no Read the byline aloud.
voices object or null no Per language tag (e.g. “en-US”): narrator, guest voice, spoken disclaimer. null removes a language.
author_voices array or null no Voice per author name; beats the per-language voice. [] clears the list.
ui_disclaimers object or null no Per language tag: the AI disclosure line under the player. “” shows none; null drops it.
pauses object or null no Seconds of silence after each meta field: a number or a [min, max] pair. “” restores the default.
player object or null no How the embedded player looks.
studio object or null no Studio-only preferences; no effect on published audio.
sharing object or null no The player share button, and which URL it shares.
email_capture object or null no Asking listeners for an email address.
extraction object or null no How the Auto Audio widget reads a page; only needed when extraction gets it wrong.
widget object or null no How the widget behaves: which hosts it runs on, and when it narrates a page.
paywall object or null no Paywall gating, and the call to action readers see.

list_mixables

Intro/outro music beds this account may use. Use the id as intro_music / outro_music on create_article or update_publication.

Argument Type Required Description
kind string no Filter by kind as reported on each row.
q string no Free-text match on name or tags.
limit integer no Page size (default 100).
starting_after string no Return beds with an id greater than this one.

create_mixable

Add a music bed of your own from an https url (25 MB max; mp3/wav/m4a/ogg/flac), usable at once as intro_music / outro_music.

Argument Type Required Description
name string yes Display name of the bed, up to 120 characters.
url string yes https URL of the audio file to fetch (mp3, wav, m4a, ogg or flac; 25 MB max).
kind enum: intro-outro, sfx, ambient no Track kind; “intro-outro” is the default.
tags object no Optional descriptive tags, each an array of up to 20 short strings.
static_duration boolean no Play the track in full instead of trimming it to the configured seconds.
publications array no Publication ids this bed is limited to; omit for the whole account.

delete_mixable

Delete one of your own music beds and clear it from any publication using it. Everlit library tracks cannot be deleted.

Argument Type Required Description
id string yes The music bed id from list_mixables.

list_playlists

Playlists in this account: name, tag rule, publications, player embed and RSS feed_url. Use the id with get_playlist / add_to_playlist.

No arguments.

get_playlist

One playlist with its pins and resolved tracks (the pins plus any filled in by its tag rule).

Argument Type Required Description
id string yes The playlist id (plist_…).

create_playlist

Create a playlist. Pin articles with articles, and/or give tags so new articles carrying them fill it, newest first.

Argument Type Required Description
name string yes Playlist name.
description string or null no Shown on the player and in the feed.
publications array or null no Restrict to these publication ids (pblc_…).
tags array or null no Fill rule: articles carrying any of these tags.
articles array or null no Articles to pin: article ids (artl_…) or job ids (ajob_…).

add_to_playlist

Pin a published article to a playlist by artl_ id, or by the job_id create_article returned. It must have finished publishing.

Argument Type Required Description
playlist string yes The playlist id (plist_…).
article string yes article id (artl_…) or job_id (ajob_…).

remove_from_playlist

Unpin an article from a playlist. Articles the tag rule filled in are not pins and cannot be removed this way.

Argument Type Required Description
playlist string yes The playlist id (plist_…).
article string yes article id (artl_…) or job_id (ajob_…).

update_playlist

Change a playlist’s rules: tags/match/ignore_tags fill it, within limits it by publish date, size caps it, order sorts it, fill false leaves only pins.

Argument Type Required Description
id string yes The playlist id (plist_…).
name string or null no Playlist name.
description string or null no Shown on the player and in the feed.
tags array or null no Fill rule: articles carrying these tags.
match enum: any, all, no Match any of the tags (default) or all of them.
ignore_tags array or null no Exclude articles carrying any of these tags.
size integer or null no How many tracks the playlist holds, 1-100.
order enum: played, viewed, latest, oldest, random, no Track order: most played, most viewed, newest, oldest, or random.
within enum: any, current_day, current_week, current_month, last_day, last_week, last_month, custom, no Only articles published in this window.
within_start string or null no Start of the window when within is “custom”.
within_end string or null no End of the window when within is “custom”.
publications array or null no Restrict to these publication ids (pblc_…).
fill boolean or null no false: pinned articles only, no tag rule.

delete_playlist

Delete a playlist: its player, page and podcast feed come down. The articles in it are NOT deleted.

Argument Type Required Description
id string yes The playlist id (plist_…).

move_in_playlist

Move a pinned article: give before, the article it should sit in front of (null moves it last), or position. Only pins can be moved.

Argument Type Required Description
playlist string yes The playlist id (plist_…).
article string yes The article to move: article id (artl_…) or job_id (ajob_…).
before string or null no Put it in front of this article (artl_…/ajob_…); null moves it last.
position integer or null no 1-based position among the pins.

get_feed

The podcast feed on a playlist: its feed_url, whether it is active, the metadata, what validation is missing, and the directories submitted to.

Argument Type Required Description
playlist string yes The playlist id (plist_…).

update_feed

Set the podcast metadata on a playlist’s feed; “” clears a field, null leaves it alone. activate_feed needs title, description, owner_email, category, artwork_url, language.

Argument Type Required Description
playlist string yes The playlist id (plist_…).
title string or null no Show title.
description string or null no Show description, as directories display it.
subtitle string or null no One-line subtitle.
summary string or null no Longer summary.
artwork_url string or null no https URL of the cover art (square, 1400-3000px).
website_url string or null no The show’s web page.
author string or null no Author shown in directories.
owner_name string or null no Feed owner name.
owner_email string or null no Feed owner email — directories send verification here.
category string or null no Apple podcast category, e.g. “News”.
subcategory string or null no Apple podcast subcategory.
language string or null no Language code, e.g. “en”.
explicit boolean or null no Explicit content flag.
keywords array or null no Keywords for discovery.
copyright string or null no Copyright line.
type enum: episodic, serial, no episodic (newest first) or serial.
complete boolean or null no true: the show is finished, no more episodes.
locked boolean or null no true: block other platforms from importing the feed.
funding_url string or null no Support/donation link.
funding_text string or null no Label for the funding link.

activate_feed

Switch the playlist’s podcast feed on, filling blanks from the playlist. 400 feed_incomplete names the fields still missing (usually artwork_url): set them with update_feed, then retry.

Argument Type Required Description
playlist string yes The playlist id (plist_…).

deactivate_feed

Stop publishing the playlist’s podcast feed; the metadata is kept, so activate_feed turns it back on.

Argument Type Required Description
playlist string yes The playlist id (plist_…).

list_feed_platforms

Where the podcast feed stands on each directory: status, the submission_url to hand the user, and platform_url once live.

Argument Type Required Description
playlist string yes The playlist id (plist_…).

mark_feed_submitted

Record that the show was submitted to a directory. Submits nothing itself: a person signs in at the submission_url from list_feed_platforms.

Argument Type Required Description
playlist string yes The playlist id (plist_…).
platform enum: apple, spotify, pocketcasts, amazon, youtube, overcast yes The directory.
notes string or null no What the user reported, e.g. the account they submitted from.
platform_url string or null no The show’s page on the directory, if it is already known.
platform_podcast_id string or null no The directory’s own id for the show, if it gave one.

mark_feed_live

Record that a directory approved the show and it is now listed; platform_url is where listeners get sent.

Argument Type Required Description
playlist string yes The playlist id (plist_…).
platform enum: apple, spotify, pocketcasts, amazon, youtube, overcast yes The directory.
platform_url string or null no The show’s page on the directory.
platform_podcast_id string or null no The directory’s own id for the show.

reset_feed_platform

Forget what was recorded for one directory: it goes back to not_started, losing its notes and platform_url. Nothing is withdrawn at the directory.

Argument Type Required Description
playlist string yes The playlist id (plist_…).
platform enum: apple, spotify, pocketcasts, amazon, youtube, overcast yes The directory.

ping_feed

Tell Overcast the feed changed so it refreshes now; other directories poll on their own schedule.

Argument Type Required Description
playlist string yes The playlist id (plist_…).

list_ingestion_feeds

The RSS/Atom feeds this account narrates automatically: the publication each feeds, its poll interval and status, last poll, and item counts.

Argument Type Required Description
publication string or null no Only feeds on this publication (pblc_…).

create_ingestion_feed

Narrate every new item of an RSS/Atom feed into a publication, polled every poll_interval_minutes. Each narration uses an article quota.

Argument Type Required Description
publication string yes The publication the items are published into (pblc_…).
url string yes The RSS or Atom feed address (http(s)).
name string or null no A label for the feed (<= 120 characters).
poll_interval_minutes integer or null no How often to check, 5 to 1440 minutes. Default 15.
max_items integer or null no Most items to narrate in one poll, 1 to 500. Null = no cap.
oldest_date string or null no ISO 8601: skip items published before this. Null = the whole feed.
status enum: active, paused no Start polling (“active”, the default) or hold it (“paused”).
poll_now boolean or null no Poll immediately as well as on the interval. Default true.

get_ingestion_feed

One ingestion feed: its publication, poll interval, status, last poll, last error and item counts.

Argument Type Required Description
id string yes The ingestion feed id (rsif_…).

update_ingestion_feed

Change a feed’s name, poll interval, item cap or date floor, or pause and resume it. The url cannot be changed - delete the feed and create another.

Argument Type Required Description
id string yes The ingestion feed id (rsif_…).
name string or null no A label for the feed (<= 120 characters).
poll_interval_minutes integer or null no How often to check, 5 to 1440 minutes.
max_items integer or null no Most items to narrate in one poll, 1 to 500; null removes the cap.
oldest_date string or null no ISO 8601 floor on item publish dates; null removes it.
status enum: active, paused no Resume (“active”) or hold (“paused”) the feed.

delete_ingestion_feed

Stop ingesting a feed and delete it with the record of items it has seen. Articles already narrated are NOT deleted.

Argument Type Required Description
id string yes The ingestion feed id (rsif_…).

poll_ingestion_feed

Check a feed now rather than waiting for its interval, one manual poll a minute. New items are narrated in the background; read list_ingestion_items.

Argument Type Required Description
id string yes The ingestion feed id (rsif_…).

list_ingestion_items

What a feed has seen, newest first: each item’s title, link, status and the article it became, or the error that stopped it.

Argument Type Required Description
id string yes The ingestion feed id (rsif_…).
status enum: pending, processing, completed, error no Only items in this state.
limit integer or null no 1 to 100; default 50.
starting_after string or null no Item id (rsii_…) to page after.

list_pronunciations

The account’s pronunciation library: how each word is read aloud, account-wide (publication null) or for one publication.

Argument Type Required Description
publication string no Only rules that apply to this publication (its own plus account-wide).

set_pronunciation

Add or update how a word is read aloud in every future narration; spell say the way it sounds. Scope it with publication, else account-wide.

Argument Type Required Description
word string yes The text as written (up to 120 characters).
say string yes How to read it, spelled phonetically (up to 300 characters).
exact_match boolean or null no Match the word only as a whole word (default false: also inside longer words).
publication string or null no Publication id (pblc_…) to scope the rule to; omit for account-wide.

delete_pronunciation

Remove a pronunciation rule (the exact word and scope it was set with).

Argument Type Required Description
word string yes The word as it was set.
publication string or null no The publication the rule was scoped to; omit for an account-wide rule.

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
400 invalid_playlist Studio rejected the playlist (see message)
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
403 terms_not_accepted The publisher has not accepted the current Everlit Terms of Service. next_step is accept_terms; resolution_url is the acceptance page. One member accepts once; access resumes immediately.
404 publication_not_found Not on your account (or outside this key’s publication scope)
404 playlist_not_found / pronunciation_not_found No playlist/pronunciation at that id or word
404 article_not_found / voice_not_found / mixable_not_found / unknown_endpoint Nothing at that address
409 job_not_complete A job_id named as article has not published yet
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)
429 concurrency_limit_exceeded As many conversions running as your account has publications; wait for one to finish (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.8.0 — 2026-09-11

  • Inline markup in article text: POST /v1/articles and create_article accept the same [everlit-pause:N], [everlit-mixable:N], [everlit-mixable-background:N] and [everlit-subvoice-open:N][everlit-subvoice-close] tags as the TTS API. Every id is checked against the account, tags are never counted in characters, the article resource reports them as markup, and an audio_url article’s text is left untouched. See Inline markup.
  • Podcast directories: GET /v1/playlists/{id}/feed/platforms lists where the show stands on Apple, Spotify, Pocket Casts, Amazon, YouTube and Overcast (with the submission_url to hand a person); POST .../platforms/{platform}/submitted and .../live record a submission and a listing; DELETE .../platforms/{platform} resets one; POST .../feed/ping asks Overcast to re-crawl. The feed resource’s distributions rows carry the same fields. MCP: list_feed_platforms, mark_feed_submitted, mark_feed_live, reset_feed_platform, ping_feed. See Directories.
  • Ingestion feeds: GET/POST /v1/ingestion-feeds, GET/PATCH/DELETE /v1/ingestion-feeds/{id}, POST .../poll and GET .../items — point a publication at an RSS or Atom feed and every item it publishes is narrated with that publication’s defaults. poll_interval_minutes (5–1440), max_items and oldest_date decide what is picked up; each narration counts against the plan’s article quota. MCP: list_ingestion_feeds, create_ingestion_feed, get_ingestion_feed, update_ingestion_feed, delete_ingestion_feed, poll_ingestion_feed, list_ingestion_items. See Ingestion feeds.

1.7.0 — 2026-09-11

  • PATCH /v1/articles/{id} and the update_article MCP tool: change an article’s title, summary, tags, cover image, publish time or privacy without re-narrating it — for every article in the account’s Studio. privacy: "unlisted" keeps the player working but hides the article from playlists, feeds and lists; a future published_at schedules it (unlisted until then) unless the same request sets privacy. See Update article.
  • audio_url on POST /v1/articles / create_article: publish your own recording instead of narrating one. The file is fetched, transcribed for read-along, mixed with the publication’s intro/outro beds (or the ones you send) and published as an ordinary audio article. title is required, text optional (the transcript fills it in); narration parameters are rejected. Such an article reports characters and voice as null, narrates no spoken AI disclaimer and carries the AI disclosure as false in its ID3 tags.
  • Playlists: PATCH /v1/playlists/{id} (name, description, tags, match, ignore_tags, size, order, within + within_start/within_end, publications, fill), DELETE /v1/playlists/{id}, and PATCH /v1/playlists/{id}/articles/{article} to reorder a pin (before or position). The playlist resource reports ignore_tags, within, within_start, within_end and fill. MCP: update_playlist, delete_playlist, move_in_playlist.
  • Podcast feeds: GET/PATCH /v1/playlists/{id}/feed and POST /v1/playlists/{id}/feed/activate / .../deactivate — set the show’s metadata, activate it (400 feed_incomplete names anything still missing; a description is always derived when none was written), submit feed_url to the directories. MCP: get_feed, update_feed, activate_feed, deactivate_feed. See Podcast feeds.
  • Publication settings now cover everything the Studio form does: an extraction block (content_selector, title_selector, date_selector, exclude_selectors, tag_field_map, site_name_tag_prefix, tag_namespaces), a widget block (allowed_origins, click_to_create, click_to_create_message, generate_on_first_view, rss_only), a paywall block, and player.theme (fourteen named colours and the title/body web fonts, merged key by key; "" restores a derived default).
  • Your own music beds: POST /v1/mixables / create_mixable uploads a bed, sound effect or ambient track from an https URL (25 MB max) with optional tags, static_duration and publications; GET and DELETE /v1/mixables/{id} / delete_mixable read or remove one you uploaded (deleting also clears it from any publication still pointing at it). Everlit’s library stays read-only (403 not_owned).

1.6.0 — 2026-09-09

  • GET /v1/articles/{id}, DELETE /v1/articles/{id} and the get_article / delete_article MCP tools now take an article id (artl_...) as well as a job id, so every article in the account’s Studio — the Auto Audio widget, WordPress, RSS, Studio uploads — is readable and deletable here. See Any article, any origin.
  • GET /v1/articles lists those alongside API jobs, newest first, and takes source=all|api|studio (all is the default). starting_after and ids accept both id forms.
  • The article resource gained source, published_at, privacy, tags, summary, authors and cover.
  • id is now the article id (artl_...) on every article; the conversion job moved to job_id (ajob_...). article_id was removed. Jobs created before this release were renamed to ajob_... ids.
  • get_article takes a job_id argument as an alternative to id.
  • MCP server version 1.9.0 → 1.12.0.

1.5.0 — 2026-09-09

  • New GET/POST /v1/playlists, GET /v1/playlists/{id}, POST/DELETE /v1/playlists/{id}/articles, and the list_playlists, get_playlist, create_playlist, add_to_playlist, remove_from_playlist MCP tools. See Playlists.
  • New GET/PUT /v1/pronunciations, DELETE /v1/pronunciations/{word}, and the list_pronunciations, set_pronunciation, delete_pronunciation MCP tools: fix how a name is read for every future narration, account-wide or per publication. See Pronunciations.
  • A get_article (REST or MCP) that finds the job still pending no longer counts against the read rate limit when it waited poll_after_seconds (30) since the previous poll of that job, so honest polling can never lock a key out; a faster re-poll still counts.
  • MCP server version 1.8.0 → 1.9.0.

1.4.0 — 2026-09-08

  • New GET /v1/mixables / list_mixables: the intro/outro music beds intro_music / outro_music may reference. See Music beds.
  • get_article accepts idempotency_key instead of id; list_articles and GET /v1/articles accept ids (up to 50) and idempotency_key, for crash recovery and checking on many jobs at once.
  • Errors carry next_step and resolution_url; see the TTS API changelog for the full list of codes — the same envelope is shared across both APIs.
  • New GET /v1/account / get_account on the shared MCP server: publisher, key, entitlements, billing, limits and plan — call it first on a new connection. See the TTS API guide.
  • MCP resources (everlit://docs/tts-api, everlit://docs/articles-api, everlit://languages) and prompts (narrate_text, publish_from_url).
  • MCP server version 1.7.0 → 1.8.0.

1.3.2 — 2026-09-05

  • Music beds can shuffle: intro_music / outro_music accept "shuffle" on POST /v1/articles and PATCH /v1/publications/{id} (and through the create_article / update_publication MCP tools) — a random track from the publication’s intro/outro music, picked fresh for every narration and never stored on the article.
  • A publication reports each bed in the same three forms it takes — an id, "shuffle", "" — so a defaults object always PATCHes back unchanged. An intro with no track set now reads as "shuffle" rather than null (that is what it has always played), an outro with none reads as "", and music is false only when neither bed plays.

1.3.1 — 2026-09-04

  • regenerate: true now really does keep the settings the article was last narrated with: the stored per-article disclaimer flags are carried into the re-narration, and an explicit disclaimer (a string, or "") in the same request still wins.
  • MCP: every optional argument of create_article and update_publication accepts null (it means “no opinion”, exactly as over HTTP), voice ids and audio_polish_level accept an integer as well as a string, and the legacy aliases mix, intro_mixable, outro_mixable, sonic_optimizer and conversation_mode are in the create_article schema again.
  • A language configured with a blank spoken disclaimer or a blank ui_disclaimers entry now narrates and shows none for that language, instead of falling through to another language’s line.

1.3.0 — 2026-09-04

  • GET /v1/publications/{id} and PATCH /v1/publications/{id}: read and change everything a publication hands its articles — voices, disclaimers and the AI disclosure line per language, per-author voices, music, Audio Polish, pauses, the player, sharing and email capture. With the get_publication / update_publication MCP tools.
  • One vocabulary whether a setting is a publication default or a per-article override: music, intro_music, outro_music, audio_polish, audio_polish_level, podcast_mode, the intro/outro duration and pad fields, language and ui_disclaimer on POST /v1/articles. mix, intro_mixable, outro_mixable, sonic_optimizer and conversation_mode keep working as aliases.
  • language on an article, and a publication default language, skip detection.
  • intro_music: "" / outro_music: "" play no bed at all — previously an unset intro let one be chosen and an unset outro fell back to the publication’s.
  • GET /v1/publications now reports each publication’s real default_voice and language_voices.

1.2.0 — 2026-09-04

  • disclaimer: "" on POST /v1/articles silences the spoken AI disclaimer for that article; omitting disclaimer or sending null uses the publication default and leaves anything stored on the article alone. The strings "false", "null", "none" and "off" are rejected with 400 invalid_parameter rather than narrated.

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://everlit.openstatus.dev