Automatically convert web content to audio
Transform any article or webpage into high-quality, professionally narrated audio with a single API call.
Overview
The Auto Audio API enables automatic conversion of web articles into high-quality audio content. Simply provide a URL, and the system will extract content, generate professional narration, apply audio mixing, and return an embeddable audio player.
Universal Compatibility
Works with any public URL. Automatic content extraction from any website.
Multi-Language
Automatic language detection with support for 11+ languages.
Audio Mixing
Professional background music, intro/outro support, and audio polish.
Smart Caching
Instant retrieval of previously converted articles with automatic update detection.
Conversation Mode
Multi-voice narration for engaging audio experiences.
CMS Integration
Native support for Fusion, NEXT_DATA, and Brightspot structured data.
https://creator.everlit.audio
Usage
The simplest way to add Everlit Auto Audio to your website is by including a widget container and the JavaScript SDK. The widget will automatically detect content on your page and convert it to audio.
Basic Implementation
Add the following HTML to your page where you want the audio player to appear:
<!-- 1. The widget container -->
<div
id="everlit-auto-audio-widget"
data-publication-id="YOUR-PUBLICATION-ID"
style="height: 136px; width: 100%;"
hidden
><a href="https://everlit.audio/" rel="nofollow">Listen to this article — audio by Everlit</a></div>
<!-- 2. The script -->
<script
defer
src="https://cdn.everlit.audio/libs/everlitAutoAudio.js"
type="text/javascript"
></script>
Widget Container
| Attribute | Required | Description |
|---|---|---|
id |
Yes | Must be everlit-auto-audio-widget |
data-publication-id |
Yes | Your unique Everlit publication ID (e.g., pblc_abc123xyz) |
hidden |
Recommended | Hides the container until audio is ready |
style |
Optional | Set dimensions; recommended height is 136px |
| Fallback link | Recommended | The link inside the container is never shown to visitors — it is replaced by the player (or stays hidden). It gives non-JavaScript crawlers a text fallback and is automatically excluded from audio conversion. |
Script Tag
| Attribute | Description |
|---|---|
defer |
Loads the script without blocking page rendering |
src |
Always use https://cdn.everlit.audio/libs/everlitAutoAudio.js |
hidden attribute
is automatically removed when the player is ready.
How It Works
When the script loads, it will:
- Detect the canonical URL of your page
- Check if audio already exists for this content
- If audio exists, display the embedded player
- If not, display a click-to-create widget (if enabled for your publication)
- Dispatch JavaScript events for each state change
Authentication
All API requests require authentication using a bearer token:
Authorization: Bearer YOUR_API_TOKEN
Endpoints
POST /audio/auto POST
Initiates audio conversion for a given URL. This endpoint is asynchronous and will either return a completed embed immediately (if cached), indicate processing is in progress, or start a new conversion job.
Request Body
{
"url": "https://example.com/article",
"publication_id": "pblc_abc123xyz",
"conversation_mode": true,
"sonic_optimizer": true
}
Required Parameters
| Parameter | Type | Description |
|---|---|---|
url |
string | The URL of the article to convert |
publication_id |
string | Your publication ID (format: pblc_xxxxx) |
Optional Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
primary_voice_id |
string | pub default | Voice ID for primary narrator |
guest_voice_id |
string | pub default | Voice ID for secondary narrator |
conversation_mode |
boolean | false | Enable multi-voice conversation mode |
sonic_optimizer |
boolean | false | Enable Audio Polish for enhanced quality |
mix |
boolean | true | Apply background music mixing |
read_urls |
boolean | false | Read URLs found in content aloud |
read_alt_text |
boolean | false | Read image alt text |
intro_mixable_id |
string | null | Custom intro audio mixable ID |
outro_mixable_id |
string | null | Custom outro audio mixable ID |
disclaimer |
string | null | Disclaimer text to read before content |
read_author_in_audio_enabled |
boolean | true | Read the author byline aloud in the audio |
Response Examples
{
"successful": true,
"embed": "<iframe src=\"https://everlit.audio/embeds/artl_abc123?eut=xyz\" frameborder=\"0\"></iframe>",
"article_id": "artl_abc123",
"metadata": {
"title": "Article Title",
"summary": "Article summary text",
"duration": 245.5
},
"dom_query_params": null,
"published_at": "2025-09-29T10:30:00Z",
"duration": 245.5,
"title": "Article Title",
"summary": "Article summary text",
"authors": ["Author Name"],
"tags": ["category:News", "author:Author Name", "language:English"],
"disclaimer_ui_text": "This audio was generated using AI"
}
{
"successful": true,
"waiting": true,
"message": "Everlit audio conversion is still in progress."
}
{
"successful": false,
"reason": "Unable to fetch the article content."
}
GET /audio/auto/check GET
Checks the status of audio conversion for a given URL. This is the recommended endpoint for polling conversion status, as it's lightweight and optimized for frequent checks.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
evp |
string | Base64-encoded JSON containing url, publication_id, and optional parameters |
EVP Encoding
The evp parameter contains URL-safe base64-encoded JSON:
// Original object
const params = {
url: "https://example.com/article",
publication_id: "pblc_abc123xyz",
eut: "optional_analytics_token"
};
// Encode to URL-safe base64
const evp = btoa(JSON.stringify(params))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
Code Examples
Complete JavaScript Implementation
async function convertToAudio(url, publicationId) {
// Check current status
const params = {
url: url,
publication_id: publicationId
};
const evp = btoa(JSON.stringify(params))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
const checkResponse = await fetch(
`https://creator.everlit.audio/audio/auto/check?evp=${evp}`
);
const checkData = await checkResponse.json();
if (checkData.embed) {
return checkData.embed; // Already ready!
}
if (checkData.create || checkData.waiting) {
// Initiate if needed
if (checkData.create) {
await fetch('https://creator.everlit.audio/audio/auto', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
});
}
// Poll for completion
return await pollForCompletion(url, publicationId);
}
}
async function pollForCompletion(url, publicationId, maxAttempts = 60) {
for (let i = 0; i < maxAttempts; i++) {
await new Promise(r => setTimeout(r, 5000)); // Wait 5s
const params = { url, publication_id: publicationId };
const evp = btoa(JSON.stringify(params))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
const response = await fetch(
`https://creator.everlit.audio/audio/auto/check?evp=${evp}`
);
const data = await response.json();
if (data.embed) return data.embed;
if (!data.waiting) throw new Error(data.reason);
}
throw new Error('Conversion timeout');
}
// Usage
const embed = await convertToAudio(
'https://example.com/article',
'pblc_abc123xyz'
);
document.getElementById('player').innerHTML = embed;
cURL Commands
# Initiate conversion
curl -X POST https://creator.everlit.audio/audio/auto \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"url": "https://example.com/article",
"publication_id": "pblc_abc123xyz",
"conversation_mode": true,
"sonic_optimizer": true
}'
# Check status (encode the parameters first)
EVP=$(echo -n '{"url":"https://example.com/article","publication_id":"pblc_abc123xyz"}' | \
base64 | tr '+/' '-_' | tr -d '=')
curl "https://creator.everlit.audio/audio/auto/check?evp=$EVP"
Python Implementation
import requests
import json
import base64
import time
class EverlitAutoAudio:
BASE_URL = 'https://creator.everlit.audio'
def __init__(self, publication_id, api_token=None):
self.publication_id = publication_id
self.api_token = api_token
def convert_article(self, url, **options):
# Check status
status = self.check_status(url)
if status.get('embed'):
return status['embed']
# Initiate if needed
if status.get('create'):
self.initiate_conversion(url, **options)
# Poll for completion
return self.poll_for_completion(url)
def check_status(self, url):
params = {'url': url, 'publication_id': self.publication_id}
evp = base64.urlsafe_b64encode(
json.dumps(params).encode()
).decode().rstrip('=')
response = requests.get(
f'{self.BASE_URL}/audio/auto/check',
params={'evp': evp}
)
return response.json()
def initiate_conversion(self, url, **options):
headers = {'Content-Type': 'application/json'}
if self.api_token:
headers['Authorization'] = f'Bearer {self.api_token}'
payload = {
'url': url,
'publication_id': self.publication_id,
**options
}
response = requests.post(
f'{self.BASE_URL}/audio/auto',
headers=headers,
json=payload
)
return response.json()
def poll_for_completion(self, url, max_attempts=60):
for _ in range(max_attempts):
time.sleep(5)
status = self.check_status(url)
if status.get('embed'):
return status['embed']
raise Exception('Conversion timeout')
# Usage
client = EverlitAutoAudio('pblc_abc123xyz', 'your_token')
embed = client.convert_article(
'https://example.com/article',
conversation_mode=True,
sonic_optimizer=True
)
print(embed)
Ruby Implementation
require 'net/http'
require 'json'
require 'base64'
class EverlitAutoAudio
BASE_URL = 'https://creator.everlit.audio'
def initialize(publication_id, api_token = nil)
@publication_id = publication_id
@api_token = api_token
end
def convert_article(url, options = {})
status = check_status(url)
return status[:embed] if status[:embed]
initiate_conversion(url, options) if status[:create]
poll_for_completion(url)
end
def check_status(url)
params = { url: url, publication_id: @publication_id }
evp = Base64.urlsafe_encode64(params.to_json, padding: false)
uri = URI("#{BASE_URL}/audio/auto/check?evp=#{evp}")
response = Net::HTTP.get_response(uri)
JSON.parse(response.body, symbolize_names: true)
end
def initiate_conversion(url, options = {})
uri = URI("#{BASE_URL}/audio/auto")
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{@api_token}" if @api_token
body = { url: url, publication_id: @publication_id }.merge(options)
request.body = body.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body, symbolize_names: true)
end
def poll_for_completion(url, max_attempts = 60)
max_attempts.times do
sleep(5)
status = check_status(url)
return status[:embed] if status[:embed]
end
raise "Conversion timeout"
end
end
# Usage
client = EverlitAutoAudio.new('pblc_abc123xyz', 'your_token')
embed = client.convert_article(
'https://example.com/article',
conversation_mode: true,
sonic_optimizer: true
)
puts embed
Error Handling
Common Error Scenarios
| Scenario | Response | Action |
|---|---|---|
| Missing URL | {"successful": false, "message": "No URL was sent..."} |
Provide valid URL parameter |
| Rate Limited | {"successful": true, "reason": "Everlit is already hard at work..."} |
Wait 30+ seconds and retry |
| URL Blocked | {"successful": false, "reason": "This URL is not available..."} |
Check whitelist settings |
| Monthly Limit | 403: {"error": "Monthly article limit reached"} |
Upgrade plan or wait for reset |
| Invalid Voice | {"successful": false, "reason": "Invalid parameters: voice_id..."} |
Use valid voice ID for your publisher |
| Extraction Failed | {"successful": false, "reason": "Unable to fetch article..."} |
Check URL accessibility and format |
Retry Strategy
async function withRetry(fn, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // Exponential backoff
await new Promise(r => setTimeout(r, delay));
}
}
}
Complete Parameter Reference
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url |
string | Yes | - | Article URL to convert |
publication_id |
string | Yes | - | Your publication ID |
eut |
string | No | auto | Analytics tracking token |
primary_voice_id |
string | No | pub setting | Primary narrator voice ID |
guest_voice_id |
string | No | pub setting | Secondary narrator voice ID |
conversation_mode |
boolean | No | false | Enable multi-voice mode |
read_urls |
boolean | No | false | Read URLs aloud |
read_alt_text |
boolean | No | false | Read image alt text |
sonic_optimizer |
boolean | No | false | Enable Audio Polish |
optimizer_level |
string | No | default | "light", "default", "heavy" |
mix |
boolean | No | true | Apply audio mixing |
intro_mixable_id |
string | No | null | Custom intro audio ID |
outro_mixable_id |
string | No | null | Custom outro audio ID |
intro_duration |
integer | No | 0 | Intro length (seconds) |
outro_duration |
integer | No | 0 | Outro length (seconds) |
intro_pad |
integer | No | 0 | Intro padding (seconds) |
outro_pad |
integer | No | 0 | Outro padding (seconds) |
disclaimer |
string | No | null | Disclaimer text |
disclaimer_voice_id |
string | No | null | Disclaimer voice ID |
read_author_in_audio_enabled |
boolean | No | true | Read author byline aloud |
disable_language_detection |
boolean | No | false | Disable auto voice selection |
click_to_create |
boolean | No | false | From click widget |
content_last_published_timestamp |
integer | No | null | Unix timestamp (ms) |
dom_content |
string | No | null | Raw page HTML for content extraction |
rendered_html |
string | No | null | Rendered HTML for media extraction |
page_url |
string | No | null | Page URL for syndicated content |
Server-Side Content Ingestion
If your system already has the article content (for example, a headless CMS or a feed
ingestion pipeline), you can supply it directly in the POST /audio/auto body
instead of relying on automatic content extraction. This produces deterministic audio that
does not depend on the quality of server-side scraping.
Modes of Operation
- Full override (scraping skipped): when the body contains both
textandtitle, the system uses your content verbatim and does not fetch or scrape the page. Theurlis still required, but only as the canonical identifier and cache key. - Per-field override (scraping fills the gaps): if you provide some content
fields but omit
textortitle(and send no structured data), the page is scraped and your supplied fields take precedence — extraction fills only the fields you leave blank. For example, send onlytextto replace the article body while letting extraction supply the title, image, and byline.
Ingestion Parameters
| Parameter | Type | Description |
|---|---|---|
text |
string | Article body to narrate. With title, fully replaces content extraction. |
title |
string | Article headline. With text, fully replaces content extraction. |
authors |
array of strings | Author bylines. Normalized, read aloud in the narration, and added as author: tags. |
custom_byline |
string | Exact byline text to read aloud, overriding the auto-generated author string. |
summary |
string | Article summary / description. |
category |
string | Single section/category. Added as a category: tag. |
art_url |
string | URL of the article's artwork/featured image. |
published_at |
string | Publication timestamp (ISO 8601). Used for deduplication and update detection. |
tags |
array of strings | Additional pass-through tags (e.g., stock tickers). |
Example (Full Override)
curl -X POST https://creator.everlit.audio/audio/auto \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"url": "https://example.com/article",
"publication_id": "pblc_abc123xyz",
"title": "City Council Approves New Transportation Plan",
"text": "The city council voted 7-2 on Tuesday to approve a comprehensive transportation plan...",
"authors": ["Jane Doe", "John Smith"],
"summary": "A comprehensive look at the new transportation plan.",
"category": "Local News",
"art_url": "https://example.com/images/council.jpg",
"published_at": "2026-05-29T10:30:00Z",
"tags": ["transportation", "city-council"]
}'
JavaScript Events
The Auto Audio widget emits custom DOM events that allow you to react to various states of the audio player lifecycle. These events are useful for showing/hiding related UI elements, tracking analytics, or integrating with your page's behavior.
Available Events
| Event Name | When Fired | Use Case |
|---|---|---|
everlit:ready |
Audio player iframe is successfully embedded | Show related UI elements, display duration |
everlit:clickToCreate |
Click-to-create widget is displayed | Show hints or call-to-action elements |
everlit:processing |
Audio conversion has started | Show loading indicators |
everlit:error |
An error occurred during check or conversion | Display error messages, hide player area |
Event Details
everlit:ready
Fired when the audio player is successfully embedded on the page. This is the primary event for most integrations.
Event Detail Properties
| Property | Type | Description |
|---|---|---|
embed |
boolean | Always true for this event |
element |
HTMLElement | The container element holding the audio player iframe |
articleId |
string | null | The Everlit article ID (e.g., "artl_abc123") |
metadata |
object | null | Article metadata object (see below) |
Metadata Object
| Property | Type | Example | Description |
|---|---|---|---|
title |
string | "Breaking: Major Event Unfolds" |
The article title |
summary |
string | "A brief overview of the article content..." |
AI-generated summary of the article |
duration |
number | 245.5 |
Audio duration in seconds |
Example Response
// Example e.detail value:
{
embed: true,
element: HTMLDivElement,
articleId: "artl_7kX9mPqR2Nw",
metadata: {
title: "City Council Approves New Transportation Plan",
summary: "The city council voted 7-2 to approve a comprehensive transportation plan that includes new bike lanes, expanded bus routes, and infrastructure improvements.",
duration: 187.3
}
}
Usage Example
document.addEventListener('everlit:ready', function(e) {
console.log('Audio player ready!', e.detail);
// Show a related element
document.querySelector('.audio-controls').style.display = 'block';
// Display formatted duration
if (e.detail.metadata?.duration) {
const minutes = Math.round(e.detail.metadata.duration / 60);
document.querySelector('.listen-time').textContent = minutes + ' min listen';
}
// Display title
if (e.detail.metadata?.title) {
document.querySelector('.audio-title').textContent = e.detail.metadata.title;
}
});
everlit:clickToCreate
Fired when a click-to-create widget is displayed (for publications with on-demand audio generation).
document.addEventListener('everlit:clickToCreate', function(e) {
// e.detail contains:
// {
// clickToCreate: true,
// processing: false, // true if already being created
// element: HTMLElement // The click widget element
// }
if (!e.detail.processing) {
// Show a hint to encourage users to click
document.querySelector('.create-audio-hint').style.display = 'block';
}
});
everlit:processing
Fired when audio conversion begins (after user clicks to create or auto-conversion starts).
document.addEventListener('everlit:processing', function(e) {
// e.detail contains:
// {
// processing: true,
// url: "https://example.com/article"
// }
// Show a loading state
document.querySelector('.audio-loading').style.display = 'block';
});
everlit:error
Fired when an error occurs during the audio check or conversion process.
document.addEventListener('everlit:error', function(e) {
// e.detail contains:
// {
// error: true,
// reason: "Error description"
// }
console.error('Everlit error:', e.detail.reason);
// Optionally hide the player area on error
document.querySelector('.audio-player-container').style.display = 'none';
});
Complete Integration Example
Here's a full example showing how to use events to control page elements based on audio availability:
<!-- Your page HTML -->
<div id="everlit-auto-audio-widget" data-publication-id="pblc_xxx"></div>
<div class="audio-info" style="display: none;">
<span class="listen-time"></span>
<button class="share-audio-btn">Share Audio</button>
</div>
<script src="https://cdn.everlit.audio/everlitAutoAudio.min.js"></script>
<script>
// Show audio info when player is ready
document.addEventListener('everlit:ready', function(e) {
const audioInfo = document.querySelector('.audio-info');
audioInfo.style.display = 'flex';
// Display duration
if (e.detail.metadata?.duration) {
const minutes = Math.round(e.detail.metadata.duration / 60);
document.querySelector('.listen-time').textContent = minutes + ' min listen';
}
// Log for analytics
console.log('Audio loaded for article:', e.detail.articleId);
});
// Hide audio info on error
document.addEventListener('everlit:error', function(e) {
document.querySelector('.audio-info').style.display = 'none';
});
</script>
document object and bubble up, so you can listen for them anywhere in your page.
Best Practices
Widget Placement
- Place the widget container early in your article template for best visibility
- Use the
hiddenattribute to prevent layout shifts before audio loads - Set a fixed height (136px recommended) to reserve space for the player
Performance Tips
- Use the
deferattribute on the script tag to avoid blocking page load - The widget automatically caches results - repeated page loads are instant
- Consider using the JavaScript events to lazy-load related UI elements
Content Optimization
- Ensure your pages have a valid canonical URL for consistent audio matching
- Use semantic HTML to help content extraction identify article body text
- Include proper meta tags (title, description, author) for better audio metadata
Error Handling
- Listen for the
everlit:errorevent to gracefully handle failures - Implement retry logic with exponential backoff for transient errors
- Use the check endpoint for status polling rather than repeatedly calling the POST endpoint
everlit:ready event with your analytics to track audio engagement alongside article pageviews.