# Create access tier Source: https://docs.beyondwords.io/api-reference/access-tiers/create post /projects/{project_id}/player_settings/access_tiers Creates an access tier for a project to control player access to content. # List access tiers Source: https://docs.beyondwords.io/api-reference/access-tiers/list get /projects/{project_id}/player_settings/access_tiers Lists access tiers configured for a project. # Get access tier Source: https://docs.beyondwords.io/api-reference/access-tiers/show get /projects/{project_id}/player_settings/access_tiers/{id} Returns a single access tier by ID. # Update access tier Source: https://docs.beyondwords.io/api-reference/access-tiers/update put /projects/{project_id}/player_settings/access_tiers/{id} Updates an access tier for a project. # Create ad Source: https://docs.beyondwords.io/api-reference/ads/create post /projects/{project_id}/ads Creates an ad in a project. # Delete ad Source: https://docs.beyondwords.io/api-reference/ads/delete delete /projects/{project_id}/ads/{id} Deletes an ad from a project. # List ads Source: https://docs.beyondwords.io/api-reference/ads/list get /projects/{project_id}/ads Lists ads configured for a project. # Get ad Source: https://docs.beyondwords.io/api-reference/ads/show get /projects/{project_id}/ads/{id} Returns a single ad by ID. # Update ad Source: https://docs.beyondwords.io/api-reference/ads/update put /projects/{project_id}/ads/{id} Updates an ad in a project. # Get ad analytics Source: https://docs.beyondwords.io/api-reference/analytics/ad-analytics get /projects/{project_id}/ads/{id}/analytics Retrieve analytics for a single ad in a project. # List ads analytics Source: https://docs.beyondwords.io/api-reference/analytics/ads-analytics get /projects/{project_id}/ads/analytics Retrieve analytics overview for all ads in a project. # Get content analytics Source: https://docs.beyondwords.io/api-reference/analytics/content-analytics get /projects/{project_id}/content/{id}/analytics Retrieve analytics for some specific content. # List content analytics Source: https://docs.beyondwords.io/api-reference/analytics/contents-analytics get /projects/{project_id}/content/analytics Retrieves an analytics overview for all content items in a project # Get organization analytics Source: https://docs.beyondwords.io/api-reference/analytics/organization-analytics get /organization/analytics Retrieves analytics for all projects within an organization # Get project analytics Source: https://docs.beyondwords.io/api-reference/analytics/project-analytics get /projects/{id}/analytics Retrieves analytics for a project # List projects analytics Source: https://docs.beyondwords.io/api-reference/analytics/projects-analytics get /projects/analytics Retrieves an analytics overview for all projects within an organization # Create background tracks Source: https://docs.beyondwords.io/api-reference/background-tracks/create post /background_tracks Creates a background track in your organization. # Delete background track Source: https://docs.beyondwords.io/api-reference/background-tracks/delete delete /background_tracks/{id} Deletes a background track from your organization. # List background tracks Source: https://docs.beyondwords.io/api-reference/background-tracks/list get /background_tracks Lists background tracks available to your organization. # Get background track Source: https://docs.beyondwords.io/api-reference/background-tracks/show get /background_tracks/{id} Returns a background track from your organization. # Update background track Source: https://docs.beyondwords.io/api-reference/background-tracks/update put /background_tracks/{id} Updates a background track in your organization. # Get content extraction settings Source: https://docs.beyondwords.io/api-reference/content-extraction-settings/show get /projects/{project_id}/content_extraction_settings Returns extraction settings for a project, including JavaScript rendering and request header configuration. # Update content extraction settings Source: https://docs.beyondwords.io/api-reference/content-extraction-settings/update put /projects/{project_id}/content_extraction_settings Updates extraction settings for a project, including extraction mode and JavaScript rendering options. # Create content filter Source: https://docs.beyondwords.io/api-reference/content-filters/create post /html_filters Creates a content filter in your organization. # Delete content filter Source: https://docs.beyondwords.io/api-reference/content-filters/delete delete /html_filters/{id} Deletes a content filter from your organization. # List content filters Source: https://docs.beyondwords.io/api-reference/content-filters/list get /html_filters Lists content filters in your organization. Content filters control which HTML elements are included or excluded during extraction. # Content filters overview Source: https://docs.beyondwords.io/api-reference/content-filters/overview Configure which HTML elements are included or excluded from audio generation You can use the API to configure which HTML elements should be included or excluded from audio generation. For example, to exclude `

` subheadings from audio generation, add a filter with `include: false`. ```json theme={null} { "element_type": "h2", "include": false } ``` Define filters by `element_type`, `element_class`, `element_id`, `element_data`, `element_xpath`, or `value`. Combine attributes for more targeted filters. See [Content extraction](/docs-and-guides/integrations/content-extraction) for how content filters work with ingestion. # Get content filter Source: https://docs.beyondwords.io/api-reference/content-filters/show get /html_filters/{id} Returns a single content filter by ID. # Update content filter Source: https://docs.beyondwords.io/api-reference/content-filters/update put /html_filters/{id} Updates a content filter in your organization. # Create and generate content Source: https://docs.beyondwords.io/api-reference/content/create post /projects/{project_id}/content Submit a content item and queue audio or video generation This is the primary endpoint for generating audio and video. Submit a new content item and BeyondWords queues processing based on the `type` you provide. See [Processing types](/api-reference/content/processing-types) for request body examples. After creating, set up [webhooks](/api-reference/webhooks/list) to be notified when processing completes via [`audio.updated`](/docs-and-guides/integrations/webhooks#audio-updated-payload). To update an existing item and re-run generation, use [Update content](/api-reference/content/update) or [Regenerate content](/api-reference/content/regenerate). # Delete content Source: https://docs.beyondwords.io/api-reference/content/delete delete /projects/{project_id}/content/{id} Deletes a content item from your project. # Generating content Source: https://docs.beyondwords.io/api-reference/content/introduction How to generate audio and video through the content API To generate audio or video, submit a content item via [Create and generate content](/api-reference/content/create). That endpoint queues TTS and media processing — it is the main entry point for integrations. Most workflows follow three steps: 1. **Create** — POST article HTML, manual segments, or an audio upload. See [Processing types](/api-reference/content/processing-types). 2. **Wait for completion** — Use [webhooks](/api-reference/webhooks/list) to be notified when `status` is `processed` (or handle `error` / `skipped`). See the [Webhooks guide](/docs-and-guides/integrations/webhooks). 3. **Retrieve** — Call [Get content](/api-reference/content/show) with `?segments=full` when you need segment data for the player. Submit content and start audio or video generation. Choose `auto_segment`, `manual_segment`, or `audio_upload`. Properties returned on every content item. Re-run generation after updating an existing item. ## Endpoints | Goal | Endpoint | | ----------------------------------------- | ------------------------------------------------------------ | | Generate audio or video for new content | [Create and generate content](/api-reference/content/create) | | Fetch a content item | [Get content](/api-reference/content/show) | | Update a content item | [Update content](/api-reference/content/update) | | Replace AI-generated audio with an upload | [Replace audio](/api-reference/content/replace-audio) | | Delete a content item | [Delete content](/api-reference/content/delete) | | Re-generate after edits | [Regenerate content](/api-reference/content/regenerate) | | Generate a summary | [Summarize content](/api-reference/content/summarize) | | Upload audio for `audio_upload` | [Upload file](/api-reference/content/upload) | | List content items | [List content](/api-reference/content/list) | The other API reference sections — **Extraction**, **Pronunciation settings**, **Script settings**, **Video settings**, and **Background music settings** — configure project defaults. They do not generate content on their own. Use the **Items** endpoints below the developer guide for that. ## Identifiers | Field | Use | | ------------ | -------------------------------------------------------------------------------------------------- | | `id` | BeyondWords UUID. Use with player and analytics endpoints. | | `source_id` | Your CMS or feed identifier. Use for upserts and [syndication](/api-reference/content/syndicated). | | `source_url` | Public article URL. Use with [player by source URL](/api-reference/player/by-source-url). | ## Processing status | Status | Meaning | | ------------ | --------------------------------------------------------------------------------------------------- | | `draft` | Created but not queued for generation. | | `queued` | Waiting to be processed. | | `processing` | Audio or video is being generated. | | `processed` | Generation complete. Audio and segment data are available. | | `skipped` | Generation was skipped (for example, insufficient credits). | | `error` | Generation failed. Update the item or call [Regenerate content](/api-reference/content/regenerate). | ## Related guides * [Content items](/docs-and-guides/content/content-items) — dashboard concepts and metadata * [Webhooks](/docs-and-guides/integrations/webhooks) — get notified when processing completes * [Replace audio](/api-reference/content/replace-audio) — swap AI-generated audio for an uploaded file via the API * [Upload audio](/docs-and-guides/tools/upload-audio) — dashboard workflow for `audio_upload` # List content Source: https://docs.beyondwords.io/api-reference/content/list get /projects/{project_id}/content Lists content items within a project. # Content object Source: https://docs.beyondwords.io/api-reference/content/overview Properties on the content resource A content item belongs to a project and is returned by [List](/api-reference/content/list), [Create](/api-reference/content/create), [Get](/api-reference/content/show), and [Update](/api-reference/content/update) content endpoints. See [Processing types](/api-reference/content/processing-types) for how `type` affects what you send in requests. For [syndication](/api-reference/content/syndicated), check `is_copy` to distinguish originals from reused audio. ## Properties | Property | Type | Description | | --------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Unique UUID for the object. | | `status` | string | Processing state: `draft`, `queued`, `processing`, `processed`, `skipped`, or `error`. | | `type` | string | `auto_segment`, `manual_segment`, or `audio_upload`. | | `title` | string | Content title. | | `summary` | string | Content summary. | | `body` | string | Content body. | | `source_id` | string | External identifier from your CMS or feed. | | `source_url` | string | Public URL of the source article. | | `author` | string | Content author. | | `image_url` | string | Feature image URL. | | `metadata` | object | Arbitrary key-value metadata (for example, `{"category": "sport"}`). | | `is_copy` | boolean | `true` when audio is reused from another project via syndication. | | `audio` | array | Generated audio files for the full content item. | | `audio.id` | integer | Audio object identifier. | | `audio.content_type` | string | MIME type of the audio file. | | `audio.url` | string | URL of the generated audio file. | | `audio.duration` | integer | Duration in milliseconds. | | `video` | array | Generated video files for the full content item. | | `segments` | array | Segments in the content item. Use `?segments=full` on GET to populate. | | `segments.id` | integer | Segment identifier. | | `segments.marker` | string | Marker for [segment-level playback](/docs-and-guides/distribution/player/developer-guides/segment-detection). | | `segments.section` | string | `title`, `summary`, or `body`. | | `segments.content_type` | string | `text`, `audio`, `image`, or `video`. | | `segments.text` | string | Plain text when `content_type` is `text`. | | `segments.audio_url` | string | Audio URL when `content_type` is `audio`. | | `segments.image_url` | string | Image URL when `content_type` is `image`. | | `segments.video_url` | string | Video URL when `content_type` is `video`. | | `segments.video_settings` | object | Settings for video segments. | | `segments.video_settings.plays_in_background` | boolean | When `true`, the clip plays under narration (timed like an image and looping as needed) instead of as a standalone scene. Defaults to `false`. | | `segments.start_time` | integer | Start time in milliseconds. | | `segments.duration` | integer | Duration in milliseconds. | | `published` | boolean | Whether the item appears in players and feeds. | | `publish_date` | string | Publish or scheduled publish time (ISO 8601). | | `ads_enabled` | boolean | Whether ads play for this item in players. | | `auto_segment_updates_enabled` | boolean | Regenerate audio when `title`, `summary`, or `body` changes (`auto_segment` only). | | `created` | string | Creation time (ISO 8601). | | `updated` | string | Last update time (ISO 8601). | # Processing types Source: https://docs.beyondwords.io/api-reference/content/processing-types How auto_segment, manual_segment, and audio_upload generate audio The `type` property on a content item determines how BeyondWords processes it. Set `type` when you [create](/api-reference/content/create) or [update](/api-reference/content/update) an item. ## auto\_segment Recommended for most publishers. Submit HTML for `body` (and optionally `title` and `summary`). BeyondWords segments the body automatically and generates audio asynchronously. ```json theme={null} { "type": "auto_segment", "title": "

Article title

", "summary": "

Summary

", "body": "

First paragraph.

Second paragraph.

", "source_id": "cms-article-123", "source_url": "https://example.com/article", "published": true } ``` The initial response returns `status: "queued"` and an empty `segments` array. After processing, call [Get content](/api-reference/content/show) with `?segments=full` to retrieve segment markers and timings. ### Pause markers Add verbal pauses in HTML: ```html theme={null}

The policy reduces emissions. In practice, it may do the opposite.

``` Pause values are in seconds (max 3), with up to one decimal place. ## manual\_segment For editorial tools or fine-grained control. Do **not** send `title`, `summary`, or `body`. Instead, send a `segments` array. | Property | Options | | -------------- | ------------------------------------------------------------------- | | `section` | `title`, `summary`, or `body` | | `content_type` | `text` (TTS), `audio` (upload URL), `image` (for video), or `video` | ```json theme={null} { "type": "manual_segment", "source_id": "cms-article-123", "segments": [ { "section": "title", "content_type": "text", "text": "Article title" }, { "section": "body", "content_type": "text", "text": "First paragraph.", "marker": "para-1" }, { "section": "body", "content_type": "text", "text": "Second paragraph.", "marker": "para-2" }, { "section": "body", "content_type": "video", "video_url": "https://example.com/clip.mp4", "video_settings": { "plays_in_background": true } } ] } ``` Use `marker` on text segments when you need [segment-level playback](/docs-and-guides/distribution/player/developer-guides/segment-detection) in the player. Set `video_settings.plays_in_background` to `true` on video segments so the clip plays under narration instead of as a standalone scene. Background clips follow visual timing, loop as needed, and do not extend the video length. For pauses in plain text, use ``. ## audio\_upload For podcasts or pre-recorded audio. Submit a `title`, `body` (shown in the player or feeds as metadata), and one audio segment: ```json theme={null} { "type": "audio_upload", "title": "Episode title", "body": "Episode description", "source_id": "episode-42", "segments": [ { "section": "title", "content_type": "audio", "audio_url": "https://example.com/episode.mp3" } ], "published": true } ``` The title and body are not converted to speech. Use [Upload file](/api-reference/content/upload) if you need to upload the audio file to BeyondWords first. To replace AI-generated audio on an existing item, see [Replace audio](/api-reference/content/replace-audio). ## Choosing a type | Type | Best for | | ---------------- | ---------------------------------------------------------- | | `auto_segment` | CMS integrations, article-to-audio pipelines | | `manual_segment` | Custom editorial UIs, mixed media, precise segment control | | `audio_upload` | Podcasts, human-recorded narration | # Regenerate content Source: https://docs.beyondwords.io/api-reference/content/regenerate post /projects/{project_id}/content/{id}/regenerate Re-run audio or video generation for an existing content item Use this endpoint when content has already been created and you need to regenerate its audio or video — for example, after updating the `title`, `body`, or `summary` on an `auto_segment` item. # Replace audio Source: https://docs.beyondwords.io/api-reference/content/replace-audio Replace AI-generated audio with an uploaded audio file You can use the API to replace the AI-generated audio on an existing content item with your own audio file. This is useful if you want to switch to a human-recorded version while keeping the same content item, player embeds, distributions, and analytics intact. The same workflow applies whether you're replacing a single item or doing it in bulk. To replace audio through the dashboard instead, go to **Content → Items**, click **••• → Replace** alongside the item, and follow the steps in [Upload audio](/docs-and-guides/tools/upload-audio#replace-an-existing-content-item). ## Replace audio on an existing content item To replace audio on an existing content item, send a `PUT` request to [Update content](/api-reference/content/update) with `"type": "audio_upload"`. This switches the content item's processing type and replaces its audio with the file you provide. The `audio_url` must be a publicly accessible URL. BeyondWords fetches the file from that URL when it processes the request — the file is not uploaded directly in the request body. ```json theme={null} { "type": "audio_upload", "auto_segment_updates_enabled": false, "segments": [ { "section": "title", "content_type": "audio", "audio_url": "https://example.com/your-replacement-audio.mp3" } ] } ``` The `{id}` path parameter accepts either the content UUID or the original `source_id` you set when creating the item. Set `"auto_segment_updates_enabled": false` to prevent future text edits from triggering AI regeneration and overwriting your uploaded audio. Without this, editing the `title`, `summary`, or `body` of the item will queue a new AI generation run. ## Create a new content item with uploaded audio If you're creating a new content item from scratch with an audio file rather than replacing an existing one, use `POST /projects/{project_id}/content` with `"type": "audio_upload"`: ```json theme={null} { "type": "audio_upload", "title": "My article title", "body": "Article body text", "source_id": "your-cms-article-id", "published": true, "segments": [ { "section": "title", "content_type": "audio", "audio_url": "https://example.com/your-audio-file.mp3" } ] } ``` See [Processing types](/api-reference/content/processing-types#audio_upload) for more on the `audio_upload` type. ## Upload a file to BeyondWords first If your audio file isn't already hosted at a public URL, use [Upload file](/api-reference/content/upload) to upload it to BeyondWords storage first. That endpoint returns a URL you can then pass as `audio_url`. # Get content Source: https://docs.beyondwords.io/api-reference/content/show get /projects/{project_id}/content/{id} Returns a single content item from your project. Use the `segments` query parameter to include segment-level data. # Summarize content Source: https://docs.beyondwords.io/api-reference/content/summarize post /projects/{project_id}/content/{id}/summarize Generates a script summary for a content item using the project script settings. # Syndication Source: https://docs.beyondwords.io/api-reference/content/syndicated Reuse audio across content items and projects to save credits Syndication lets publishers reuse audio across different projects — such as multiple websites — without generating new audio for each content item. Credits are only consumed when audio is **generated**. A syndicated copy reuses an existing audio file, so you are not charged again for TTS on that item. This feature must be enabled by the BeyondWords team. [Contact us](/docs-and-guides/support/get-support) to discuss enabling it for your account. ## Why use syndication | Benefit | Detail | | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | **Save credits** | The first content item with a given `source_id` generates audio as usual. Subsequent items in other projects reuse that audio and skip generation. | | **Consistency** | The same spoken audio plays across every site or project that syndicates the item. | | **Per-project distribution** | Each project still gets its own content item, so you can use different `source_url`, `metadata`, playlists, and analytics per site. | ## Example A publisher runs two websites (separate projects) and publishes the same article on both: 1. **Project A** — [Create content](/api-reference/content/create) with `source_id: "shared-article-001"`. Audio is generated and credits are consumed. 2. **Project B** — Create content with the same `source_id`. A new content item is created, but it references Project A's audio file. No new audio is generated and no additional TTS credits are used. ## How it works When you POST to [Create content](/api-reference/content/create) with a `source_id`: 1. If no item with that `source_id` exists in your organization, audio is generated as usual. 2. If an item with that `source_id` exists in another project, the existing audio file is reused. A new content item is still created in the target project. Matching is by `source_id` across projects within your organization. ## Constraints Syndication trades flexibility for credit savings. Keep these limitations in mind: * **Feature gate** — Syndication must be enabled on your account before it takes effect. * **Matching key** — Reuse only happens when the `source_id` matches an existing item in another project. Different IDs always trigger new generation. * **Locked audio content** — Syndicated copies cannot have different spoken content. Inherited fields cannot be overwritten on the child item (see below). * **Parent updates** — Changes to the parent item's audio or text flow to syndicated copies. You cannot independently edit title, body, summary, or segments on a child. * **Project-scoped metadata only** — You can set project-specific values such as `source_url` and `metadata`, but not the core audio-bearing fields. ## Identifying copies Use `is_copy` in the API response or the [`audio.updated`](/docs-and-guides/integrations/webhooks#audio-updated-payload) webhook payload: | Value | Meaning | | ---------------- | ------------------------------------------------------------------- | | `is_copy: false` | Original item that generated the audio. Credits were consumed here. | | `is_copy: true` | Child item that reuses existing audio. No TTS credits consumed. | ## Inherited fields When audio is reused, these fields are inherited from the parent and **cannot be overwritten** on the child: * `status`, `title`, `type`, `summary`, `body`, `audio`, `video`, `summarization`, `segments` You can still set project-specific fields such as `source_url` and `metadata` in the create request. # Update content Source: https://docs.beyondwords.io/api-reference/content/update put /projects/{project_id}/content/{id} Updates a content item in your project. **Segment replace semantics:** When you include a `segments` array, it becomes the new complete set of segments. Segments with an `id` are kept or updated; segments without an `id` are added; segments omitted from the array are deleted. **Regeneration:** Only changed segments are re-synthesized — unchanged segment audio is reused. A PUT that changes segments triggers regeneration automatically. Use POST `/content/{id}/regenerate` only when you need to regenerate without segment changes (for example, after updating voice or preprocessing settings). # Upload file Source: https://docs.beyondwords.io/api-reference/content/upload post /upload Uploads a file to BeyondWords storage. Use the returned URL when creating or updating content items that reference uploaded audio or other assets. # Ghost posts webhook Source: https://docs.beyondwords.io/api-reference/ghost/posts post /projects/{project_id}/integrations/ghost/posts Webhook endpoint that Ghost calls when posts are created or updated. Creates or updates content in BeyondWords when a post is published or scheduled. Webhook endpoint that Ghost calls when posts are created or updated. Creates or updates content in BeyondWords when a post is published or scheduled. Configure this URL in your Ghost custom integration. Authenticate requests with the `api_key` query parameter using your project API key. See the [Ghost integration guide](/docs-and-guides/integrations/publishing-platforms/ghost). # Get Ghost integration Source: https://docs.beyondwords.io/api-reference/ghost/show get /projects/{project_id}/integrations/ghost Returns Ghost integration settings for a project, including whether the integration is enabled and the configured API URL. Returns the Ghost integration settings for a project, including whether the integration is enabled and the configured API URL. See the [Ghost integration guide](/docs-and-guides/integrations/publishing-platforms/ghost) for dashboard setup. # Test Ghost integration Source: https://docs.beyondwords.io/api-reference/ghost/test post /projects/{project_id}/integrations/ghost/test Test the Ghost API URL and Admin API key before saving integration settings. Test the Ghost API URL and Admin API key before saving integration settings. # Update Ghost integration Source: https://docs.beyondwords.io/api-reference/ghost/update put /projects/{project_id}/integrations/ghost Enable or update the Ghost integration for a project. Set `enabled`, `api_url`, and `admin_api_key` to connect BeyondWords to your Ghost site. Enable or update the Ghost integration for a project. Set `enabled`, `api_url`, and `admin_api_key` to connect BeyondWords to your Ghost site. See the [Ghost integration guide](/docs-and-guides/integrations/publishing-platforms/ghost) for setup instructions. # List languages Source: https://docs.beyondwords.io/api-reference/languages/list get /organization/languages Lists languages available to your organization. # Get Magic Embed settings Source: https://docs.beyondwords.io/api-reference/magic-embed/show get /projects/{project_id}/integrations/client_side Returns Magic Embed settings for a project, including whether the integration is enabled and the list of allowed domains. Returns Magic Embed settings for a project, including whether the integration is enabled and the list of allowed domains. See the [Magic Embed guide](/docs-and-guides/integrations/magic-embed) for setup instructions. # Update Magic Embed settings Source: https://docs.beyondwords.io/api-reference/magic-embed/update put /projects/{project_id}/integrations/client_side Enable or update Magic Embed for a project. Set `enabled` and `allowed_domains` to control where the Magic Embed script can ingest content. Enable or update Magic Embed for a project. Set `enabled` and `allowed_domains` to control where the Magic Embed script can ingest content. See the [Magic Embed guide](/docs-and-guides/integrations/magic-embed) for setup instructions. # Overview Source: https://docs.beyondwords.io/api-reference/overview Learn about the BeyondWords API The BeyondWords API is a RESTful API that provides headless access to the entire platform. It has predictable resource-oriented URLs, accepts form-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs. This reference covers the endpoints most integrators need. ## Base URL All requests use the `v1` API. Prepend this base URL to every endpoint path: ```text theme={null} https://api.beyondwords.io/v1/ ``` For example, listing content items for a project: ```text theme={null} https://api.beyondwords.io/v1/projects/{project_id}/content ``` Replace `{project_id}` with your Project ID from **Settings → Integrations → API** in the dashboard. ## Authentication Every request requires your API key in the `X-Api-Key` header. Find your Project ID and API Key under **Settings → Integrations → API**. ```bash theme={null} -H "X-Api-Key: YOUR_API_KEY" ``` Keep your API key secret. Do not expose it in client-side code or public repositories. ## Rate limits To help keep our service stable and reliable, API requests are subject to the following rate limits: | Request type | Limit | | ------------------ | --------------------------- | | `GET` requests | 100 requests per 60 seconds | | All other requests | 25 requests per 60 seconds | ### Handling rate limits Design your integration to avoid exceeding these limits. We recommend implementing client-side rate limiting so requests are spread evenly over time rather than sent in large bursts. If your application receives a `429 Too Many Requests` response, it should wait before retrying the request. We recommend retrying after approximately 30 seconds. ### Recommended request patterns To avoid unnecessary API usage and improve reliability: * Do not continuously poll API endpoints for status updates. * Use [webhooks](/api-reference/webhooks/list) where possible, especially for status or state-change events. * Add a global concurrency limit across your application, not just per worker or per process. * Cache `GET` responses where appropriate. * Avoid running multiple parallel jobs that may unintentionally create request spikes. Following these practices will help your integration remain reliable and avoid temporary throttling. ## Resources ### Content Content items are the core resource. See the [developer guide](/api-reference/content/introduction) for processing types, the [content object](/api-reference/content/overview), and [syndication](/api-reference/content/syndicated). **Example: create content with `auto_segment`** Send `title` as plain text. `body` should be HTML. ```bash Request theme={null} curl -X POST "https://api.beyondwords.io/v1/projects/{project_id}/content" \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "auto_segment", "title": "Article title", "body": "

First paragraph.

Second paragraph.

", "source_id": "cms-article-123", "source_url": "https://example.com/article" }' ``` ```json Response theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "queued", "type": "auto_segment", "title": "Article title", "body": "

First paragraph.

Second paragraph.

", "source_id": "cms-article-123", "source_url": "https://example.com/article", "published": true, "segments": [], "audio": [], "video": [], "created": "2025-06-08T10:00:00.000Z", "updated": "2025-06-08T10:00:00.000Z" } ```
Set up [webhooks](/api-reference/webhooks/list) to be notified when processing completes. See the [Webhooks guide](/docs-and-guides/integrations/webhooks) for setup. **Endpoints** * [Create and generate content](/api-reference/content/create) * [Get content](/api-reference/content/show) * [Update content](/api-reference/content/update) * [Delete content](/api-reference/content/delete) * [Regenerate content](/api-reference/content/regenerate) * [Summarize content](/api-reference/content/summarize) * [Upload file](/api-reference/content/upload) * [List content](/api-reference/content/list) ### Extraction Control how text is extracted from webpages when ingesting content. **Filters** * [Content filters overview](/api-reference/content-filters/overview) * [List content filters](/api-reference/content-filters/list) * [Create content filter](/api-reference/content-filters/create) * [Get content filter](/api-reference/content-filters/show) * [Update content filter](/api-reference/content-filters/update) * [Delete content filter](/api-reference/content-filters/delete) **Extraction settings** * [Get content extraction settings](/api-reference/content-extraction-settings/show) * [Update content extraction settings](/api-reference/content-extraction-settings/update) ### Pronunciation settings * [List pronunciation rules](/api-reference/rules/list) * [Create pronunciation rule](/api-reference/rules/create) * [Get pronunciation rule](/api-reference/rules/show) * [Update pronunciation rule](/api-reference/rules/update) * [Delete pronunciation rule](/api-reference/rules/delete) ### Script settings * [Get project script settings](/api-reference/summarization-settings/show) * [Update project script settings](/api-reference/summarization-settings/update) **Script templates** * [List script templates](/api-reference/summarization-settings-templates/list) * [Create script template](/api-reference/summarization-settings-templates/create) * [Get script template](/api-reference/summarization-settings-templates/show) * [Update script template](/api-reference/summarization-settings-templates/update) * [Delete script template](/api-reference/summarization-settings-templates/delete) ### Video settings * [Get video settings](/api-reference/video-settings/show) * [Update video settings](/api-reference/video-settings/update) **Video templates** * [List video settings templates](/api-reference/video-settings-templates/list) * [Create video settings template](/api-reference/video-settings-templates/create) * [Get video settings template](/api-reference/video-settings-templates/show) * [Update video settings template](/api-reference/video-settings-templates/update) * [Delete video settings template](/api-reference/video-settings-templates/delete) * [Preview video settings template](/api-reference/video-settings-templates/preview) ### Background music settings * [List background tracks](/api-reference/background-tracks/list) * [Create background tracks](/api-reference/background-tracks/create) * [Get background track](/api-reference/background-tracks/show) * [Update background track](/api-reference/background-tracks/update) * [Delete background track](/api-reference/background-tracks/delete) ### Voices **Voices** * [List voices](/api-reference/voices/list) * [Get voice](/api-reference/voices/show) * [Update voice](/api-reference/voices/update) * [Delete voice](/api-reference/voices/delete) **Voice generation** * [Design voice previews](/api-reference/voices/design-previews) * [Finalize voice design](/api-reference/voices/finalize-design) **Speakers** * [List speakers](/api-reference/speakers/list) * [Create speaker](/api-reference/speakers/create) * [Get speaker](/api-reference/speakers/show) * [Update speaker](/api-reference/speakers/update) * [Delete speaker](/api-reference/speakers/delete) **Voice cloning** * [Create voice](/api-reference/voices/create) * [Clone instant voice](/api-reference/voices/clone) **Languages** * [List languages](/api-reference/languages/list) ### Distribution **Player** * [Get player by content ID](/api-reference/player/by-content-id) * [Get player by source ID](/api-reference/player/by-source-id) * [Get player by source URL](/api-reference/player/by-source-url) * [Get player by playlist ID](/api-reference/player/by-playlist-id) * [Get player by multiple identifiers](/api-reference/player/by-identifiers) **Player settings** * [Get player settings](/api-reference/player-settings/show) * [Update player settings](/api-reference/player-settings/update) **Access tiers** * [List access tiers](/api-reference/access-tiers/list) * [Create access tier](/api-reference/access-tiers/create) * [Get access tier](/api-reference/access-tiers/show) * [Update access tier](/api-reference/access-tiers/update) **Playlists** * [List playlists](/api-reference/playlists/list) * [Create playlist](/api-reference/playlists/create) * [Get playlist](/api-reference/playlists/show) * [Update playlist](/api-reference/playlists/update) * [Delete playlist](/api-reference/playlists/delete) **Podcast feeds** * [Get podcast feed settings](/api-reference/podcast-feeds/get-settings) * [Update podcast feed settings](/api-reference/podcast-feeds/update-settings) ### Integrations **Webhooks** * [List webhooks](/api-reference/webhooks/list) * [Create webhook](/api-reference/webhooks/create) * [Get webhook](/api-reference/webhooks/show) * [Update webhook](/api-reference/webhooks/update) * [Delete webhook](/api-reference/webhooks/delete) * [Test webhook](/api-reference/webhooks/test) **RSS feed importer** * [List RSS feed imports](/api-reference/rss-feed-importer/list) * [Create RSS feed import](/api-reference/rss-feed-importer/create) * [Get RSS feed import](/api-reference/rss-feed-importer/show) * [Update RSS feed import](/api-reference/rss-feed-importer/update) * [Delete RSS feed import](/api-reference/rss-feed-importer/delete) * [Run RSS feed import](/api-reference/rss-feed-importer/run) * [Test RSS feed import](/api-reference/rss-feed-importer/test) **Magic Embed** * [Get Magic Embed settings](/api-reference/magic-embed/show) * [Update Magic Embed settings](/api-reference/magic-embed/update) **Ghost** * [Get Ghost integration](/api-reference/ghost/show) * [Update Ghost integration](/api-reference/ghost/update) * [Test Ghost integration](/api-reference/ghost/test) * [Ghost posts webhook](/api-reference/ghost/posts) ### Analytics **Content** * [Get organization analytics](/api-reference/analytics/organization-analytics) * [List projects analytics](/api-reference/analytics/projects-analytics) * [Get project analytics](/api-reference/analytics/project-analytics) * [List content analytics](/api-reference/analytics/contents-analytics) * [Get content analytics](/api-reference/analytics/content-analytics) **Ads** * [List ads analytics](/api-reference/analytics/ads-analytics) * [Get ad analytics](/api-reference/analytics/ad-analytics) ### Monetization **Ads** * [List ads](/api-reference/ads/list) * [Create ad](/api-reference/ads/create) * [Get ad](/api-reference/ads/show) * [Update ad](/api-reference/ads/update) * [Delete ad](/api-reference/ads/delete) ### Organization **Projects** * [List projects](/api-reference/projects/list) * [Create project](/api-reference/projects/create) * [Get project](/api-reference/projects/show) * [Update project](/api-reference/projects/update) * [Delete project](/api-reference/projects/delete) * [Get project voices](/api-reference/projects/voices) # Get player settings Source: https://docs.beyondwords.io/api-reference/player-settings/show get /projects/{project_id}/player_settings Get the player settings for your project # Update player settings Source: https://docs.beyondwords.io/api-reference/player-settings/update put /projects/{project_id}/player_settings Updates the player settings for a project # Get player by content ID Source: https://docs.beyondwords.io/api-reference/player/by-content-id get /projects/{project_id}/player/by_content_id/{id} Returns player configuration and media for a content item, looked up by its BeyondWords content ID or legacy ID. # Get player by multiple identifiers Source: https://docs.beyondwords.io/api-reference/player/by-identifiers get /projects/{project_id}/player/by_identifiers/{array} Returns player configuration and media for a content item, looked up by multiple identifiers passed as a URL-encoded JSON array. # Get player by playlist ID Source: https://docs.beyondwords.io/api-reference/player/by-playlist-id get /projects/{project_id}/player/by_playlist_id/{id} Returns player configuration and media for a playlist, including continuous playback across its items. # Get player by source ID Source: https://docs.beyondwords.io/api-reference/player/by-source-id get /projects/{project_id}/player/by_source_id/{id} Returns player configuration and media for a content item, looked up by the `source_id` you assigned when creating it. # Get player by source URL Source: https://docs.beyondwords.io/api-reference/player/by-source-url get /projects/{project_id}/player/by_source_url/{url} Returns player configuration and media for a content item, looked up by its canonical `source_url`. # Create playlist Source: https://docs.beyondwords.io/api-reference/playlists/create post /projects/{project_id}/playlists Creates a playlist in a project. # Delete playlist Source: https://docs.beyondwords.io/api-reference/playlists/delete delete /projects/{project_id}/playlists/{id} Deletes a custom playlist from a project. # List playlists Source: https://docs.beyondwords.io/api-reference/playlists/list get /projects/{project_id}/playlists Lists playlists configured for a project. # Get playlist Source: https://docs.beyondwords.io/api-reference/playlists/show get /projects/{project_id}/playlists/{id} Returns a single playlist by ID. # Update playlist Source: https://docs.beyondwords.io/api-reference/playlists/update put /projects/{project_id}/playlists/{id} Updates a playlist in a project. # Get podcast feed settings Source: https://docs.beyondwords.io/api-reference/podcast-feeds/get-settings get /projects/{project_id}/playlists/{playlist_id}/settings Returns RSS feed settings for a playlist. Gets the podcast feed settings for a playlist. Each podcast feed is linked to a playlist — use the playlist ID when calling this endpoint. # Update podcast feed settings Source: https://docs.beyondwords.io/api-reference/podcast-feeds/update-settings put /projects/{project_id}/playlists/{playlist_id}/settings Updates RSS feed settings for a playlist. Updates the podcast feed settings for a playlist. Each podcast feed is linked to a playlist — use the playlist ID when calling this endpoint. # Create project Source: https://docs.beyondwords.io/api-reference/projects/create post /projects Creates a project in your organization. # Delete project Source: https://docs.beyondwords.io/api-reference/projects/delete delete /projects/{id} Deletes a project from your organization. # List projects Source: https://docs.beyondwords.io/api-reference/projects/list get /projects Lists all projects in your organization. # Get project Source: https://docs.beyondwords.io/api-reference/projects/show get /projects/{id} Returns a single project by ID. # Update project Source: https://docs.beyondwords.io/api-reference/projects/update put /projects/{id} Updates a project in your organization. # Get project voices Source: https://docs.beyondwords.io/api-reference/projects/voices get /projects/{id}/voices Lists the voices assigned to a project. # Create RSS feed import Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/create post /projects/{project_id}/integrations/rss_importer/feeds Creates an RSS feed import for a project. BeyondWords fetches the feed on a schedule and creates content items from matching entries. # Delete RSS feed import Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/delete delete /projects/{project_id}/integrations/rss_importer/feeds/{id} Deletes an RSS feed import from a project. # List RSS feed imports Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/list get /projects/{project_id}/integrations/rss_importer/feeds Lists RSS feed imports configured for a project. # Run RSS feed import Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/run post /projects/{project_id}/integrations/rss_importer/feeds/{id}/run Runs an RSS feed import immediately, bypassing the configured import interval. # Get RSS feed import Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/show get /projects/{project_id}/integrations/rss_importer/feeds/{id} Returns a single RSS feed import by ID. # Test RSS feed import Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/test post /projects/{project_id}/integrations/rss_importer/feeds/test Fetches and parses an RSS feed without saving an import. Use this to preview which items would be ingested and verify field mapping. # Update RSS feed import Source: https://docs.beyondwords.io/api-reference/rss-feed-importer/update put /projects/{project_id}/integrations/rss_importer/feeds/{id} Updates an RSS feed import. You can change the feed URL, field mapping, import interval, and whether the import is enabled. # Create pronunciation rule Source: https://docs.beyondwords.io/api-reference/rules/create post /rules Creates a pronunciation rule in your organization. # Delete pronunciation rule Source: https://docs.beyondwords.io/api-reference/rules/delete delete /rules/{id} Deletes a pronunciation rule from your organization. # List pronunciation rules Source: https://docs.beyondwords.io/api-reference/rules/list get /rules Lists pronunciation rules in your organization. # Get pronunciation rule Source: https://docs.beyondwords.io/api-reference/rules/show get /rules/{id} Returns a pronunciation rule from your organization. # Update pronunciation rule Source: https://docs.beyondwords.io/api-reference/rules/update put /rules/{id} Updates a pronunciation rule in your organization. # Create speaker Source: https://docs.beyondwords.io/api-reference/speakers/create post /speakers Creates a speaker in your organization Creates a speaker in your organization. Required before creating a [Clone instant voice](/api-reference/voices/clone) or [Create voice](/api-reference/voices/create) clone. See [Voice cloning](/docs-and-guides/voices/voice-cloning) for the full workflow. # Delete speaker Source: https://docs.beyondwords.io/api-reference/speakers/delete delete /speakers/{id} Deletes a speaker Deletes a speaker from your organization. # List speakers Source: https://docs.beyondwords.io/api-reference/speakers/list get /speakers Lists all speakers in your organization Lists speakers in your organization. Speakers represent the people behind custom and cloned voices. Use the returned `id` as `speaker_id` when you [create a voice](/api-reference/voices/create). # Get speaker Source: https://docs.beyondwords.io/api-reference/speakers/show get /speakers/{id} Gets a speaker from your organization Gets a speaker from your organization, including associated voices. # Update speaker Source: https://docs.beyondwords.io/api-reference/speakers/update put /speakers/{id} Updates a speaker in your organization Updates a speaker in your organization. # Create script template Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/create post /summarization_settings_templates Creates a script template in your organization. # Delete script template Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/delete delete /summarization_settings_templates/{slug} Deletes a script template from your organization. # List script templates Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/list get /summarization_settings_templates Lists script templates available in your organization. # Get script template Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/show get /summarization_settings_templates/{slug} Returns a single script template by slug. # Update script template Source: https://docs.beyondwords.io/api-reference/summarization-settings-templates/update put /summarization_settings_templates/{slug} Updates a script template in your organization. # Get project script settings Source: https://docs.beyondwords.io/api-reference/summarization-settings/show get /projects/{project_id}/summarization_settings Returns script settings for a project, including AI model configuration used when generating scripts and summaries. # Update project script settings Source: https://docs.beyondwords.io/api-reference/summarization-settings/update put /projects/{project_id}/summarization_settings Updates script settings for a project, including the default AI model, prompt, and template used for script generation. # Create video settings template Source: https://docs.beyondwords.io/api-reference/video-settings-templates/create post /video_settings_templates Creates a video settings template in your organization. # Delete video settings template Source: https://docs.beyondwords.io/api-reference/video-settings-templates/delete delete /video_settings_templates/{slug} Deletes a video settings template from your organization. # List video settings templates Source: https://docs.beyondwords.io/api-reference/video-settings-templates/list get /video_settings_templates Lists video settings templates available to your organization. # Preview video settings template Source: https://docs.beyondwords.io/api-reference/video-settings-templates/preview post /video_settings_templates/preview Generates a preview video from a video settings template. # Get video settings template Source: https://docs.beyondwords.io/api-reference/video-settings-templates/show get /video_settings_templates/{slug} Returns a video settings template from your organization. # Update video settings template Source: https://docs.beyondwords.io/api-reference/video-settings-templates/update put /video_settings_templates/{slug} Updates a video settings template in your organization. # Get video settings Source: https://docs.beyondwords.io/api-reference/video-settings/show get /projects/{project_id}/video_settings Returns video generation settings for a project. # Update video settings Source: https://docs.beyondwords.io/api-reference/video-settings/update put /projects/{project_id}/video_settings Updates video generation settings for a project. # Clone instant voice Source: https://docs.beyondwords.io/api-reference/voices/clone post /organization/voices/{id}/clone Clones a voice from cloning_consent and an uploaded utterance # Create voice Source: https://docs.beyondwords.io/api-reference/voices/create post /organization/voices Creates a voice in your organization. Creates a custom voice in your organization. For instant and professional clones, create a [speaker](/api-reference/speakers/create) first and pass its `id` as `speaker_id`. For generated voices, use [Design voice previews](/api-reference/voices/design-previews) and [Finalize voice design](/api-reference/voices/finalize-design) instead. # Delete voice Source: https://docs.beyondwords.io/api-reference/voices/delete delete /organization/voices/{id} Deletes a voice from your organization. # Design voice previews Source: https://docs.beyondwords.io/api-reference/voices/design-previews post /organization/voices/design_previews Generate voice preview audio from a text description Generate voice preview audio from a text description. Use this as the first step in [voice generation](/docs-and-guides/voices/voice-generation). After choosing a preview, call [Finalize voice design](/api-reference/voices/finalize-design) to save the voice. # Finalize voice design Source: https://docs.beyondwords.io/api-reference/voices/finalize-design post /organization/voices/finalize_design Create a permanent voice from a generated design preview Create a permanent voice from a generated design preview. Use this as the second step in [voice generation](/docs-and-guides/voices/voice-generation), after [Design voice previews](/api-reference/voices/design-previews). # List voices Source: https://docs.beyondwords.io/api-reference/voices/list get /organization/voices Lists all voices available to your organization. # Get voice Source: https://docs.beyondwords.io/api-reference/voices/show get /organization/voices/{id} Returns a voice available to your organization. # Update voice Source: https://docs.beyondwords.io/api-reference/voices/update put /organization/voices/{id} Updates a voice in your organization. # Create webhook Source: https://docs.beyondwords.io/api-reference/webhooks/create post /projects/{project_id}/webhooks Creates a webhook for a project to receive HTTP notifications when content changes. Deliveries use the `audio.updated` action type. See `#/components/examples/audioUpdatedWebhookDeliveryExample` for a representative payload (media IDs are integers, `content_type` values are `mp3`/`m3u8`/`mp4`, and `duration` is in seconds). # Delete webhook Source: https://docs.beyondwords.io/api-reference/webhooks/delete delete /projects/{project_id}/webhooks/{id} Deletes a webhook from a project. # List webhooks Source: https://docs.beyondwords.io/api-reference/webhooks/list get /projects/{project_id}/webhooks Lists webhooks configured for a project. # Get webhook Source: https://docs.beyondwords.io/api-reference/webhooks/show get /projects/{project_id}/webhooks/{id} Returns a single webhook by ID. # Test webhook Source: https://docs.beyondwords.io/api-reference/webhooks/test post /projects/{project_id}/webhooks/test Sends a test payload to a project webhook URL to verify delivery. # Update webhook Source: https://docs.beyondwords.io/api-reference/webhooks/update put /projects/{project_id}/webhooks/{id} Updates a webhook for a project. # Advert Link Source: https://docs.beyondwords.io/components/html/advert-link Links to the current advert's click-through URL. ```js AdvertLink.js theme={null} import { newEvent } from "@/lib/newEvent"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; const getHostName = (url) => { try { return new URL(url).hostname.replace(/^www\./, ""); } catch { return null; } }; export class BwAdvertLink extends BwPlayerConsumer { _link = null; _listener = null; constructor() { super(); this._link = document.createElement("a"); this._link.setAttribute("target", "_blank"); this._link.setAttribute("rel", "noreferrer"); this._link.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedAdvertLink", description: "The advert link was pressed to open the click-through URL in a new tab.", initiatedBy: "user", }), ); }); } #render() { const advert = this._player?.adverts?.[this._player.advertIndex]; if (!advert) { this._link.remove(); return; } this._link.setAttribute("href", advert.clickThroughUrl); this._link.textContent = getHostName(advert.clickThroughUrl) ?? ""; if (!this._link.isConnected) this.appendChild(this._link); } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => this.#render()); this.#render(); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-advert-link", BwAdvertLink); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add advert-link --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add advert-link --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add advert-link --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add advert-link --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the link inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor, reads the current advert (`adverts[advertIndex]`) from it, and renders a link to its `clickThroughUrl` (showing the hostname as the label), updating on every player event. On click it emits a `PressedAdvertLink` event (built with [`newEvent`](/components/html/new-event)). It renders nothing when there's no active advert, so it's safe to include unconditionally. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `adverts`, `advertIndex` | | **Events emitted** | `PressedAdvertLink` | | **Requires** | a `` ancestor and the player UMD global loaded | # BeyondWords Logo Source: https://docs.beyondwords.io/components/html/beyondwords-logo A BeyondWords logo that links back to beyondwords.io with analytics attribution. ```js BeyondWordsLogo.js theme={null} import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; const LOGO_SVG = ``; export class BwBeyondWordsLogo extends BwPlayerConsumer { _link = null; _listener = null; constructor() { super(); this._link = document.createElement("a"); this._link.target = "_blank"; this._link.rel = "noreferrer"; this._link.style.display = "inline-flex"; this._link.style.cursor = "pointer"; this._link.innerHTML = LOGO_SVG; } #render() { const analyticsId = this._player?.analyticsId ?? ""; this._link.href = `https://beyondwords.io/?utm_source=${encodeURIComponent(window.location.origin)}&utm_medium=player&utm_campaign=${analyticsId}`; } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => this.#render()); this.#render(); this.appendChild(this._link); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-beyondwords-logo", BwBeyondWordsLogo); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add beyondwords-logo --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add beyondwords-logo --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add beyondwords-logo --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add beyondwords-logo --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the element inside a ``. ```html theme={null} ``` `` renders the BeyondWords logo as a link to beyondwords.io, tagged with UTM parameters and the player's `analyticsId` (read from the nearest [``](/components/html/player-provider) ancestor). The SVG uses `currentColor`, so it inherits the surrounding text color — style it by setting the color on a parent. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `analyticsId` | | **Events emitted** | None | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Call To Action Source: https://docs.beyondwords.io/components/html/call-to-action Displays the player's call-to-action text, with a fallback when none is set. ```js CallToAction.js theme={null} import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwCallToAction extends BwPlayerConsumer { _listener = null; #render() { this.textContent = this._player?.callToAction ?? "Listen to this article"; } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => this.#render()); this.#render(); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-call-to-action", BwCallToAction); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add call-to-action --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add call-to-action --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add call-to-action --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add call-to-action --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the element inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor and reads `callToAction` from it, updating on every player event. When the source has no call-to-action configured, it falls back to `"Listen to this article"`. Edit the fallback or the markup to match your UI. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `callToAction` | | **Events emitted** | None (display only) | | **Requires** | a `` ancestor and the player UMD global loaded | # Content Title Source: https://docs.beyondwords.io/components/html/content-title Displays the title of the current content item. ```js ContentTitle.js theme={null} import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwContentTitle extends BwPlayerConsumer { _listener = null; #render() { this.textContent = this._player?.content?.[this._player.contentIndex]?.title ?? ""; } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => this.#render()); this.#render(); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-content-title", BwContentTitle); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add content-title --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add content-title --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add content-title --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add content-title --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the title inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor and reads `content` and `contentIndex` from it, updating on every player event. It renders the current item's title (`content[contentIndex].title`) as its text content. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `content`, `contentIndex` | | **Events emitted** | None (display only) | | **Requires** | a `` ancestor and the player UMD global loaded | # Download Button Source: https://docs.beyondwords.io/components/html/download-button Downloads the current item's audio as an MP3. ```js DownloadButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { downloadSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwDownloadButton extends BwPlayerConsumer { _btn = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.setAttribute("aria-label", "Download audio"); this._btn.innerHTML = downloadSvg(20); this._btn.addEventListener("click", () => { if (!this._player) return; const content = this._player.content; const contentIndex = this._player.contentIndex; const audioIndex = content[contentIndex]?.audio?.findIndex((a) => a.url?.endsWith(".mp3")); if (typeof audioIndex !== "number" || audioIndex === -1) return; this._player.onEvent( newEvent({ type: "PressedDownload", description: "The download button was pressed.", contentIndex, audioIndex, }), ); }); } connectedCallback() { super.connectedCallback(); this.appendChild(this._btn); } } customElements.define("bw-download-button", BwDownloadButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add download-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add download-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add download-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add download-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. On click, it finds the current item's MP3 audio and emits a `PressedDownload` event (built with [`newEvent`](/components/html/new-event)) carrying the `contentIndex` and `audioIndex`. If the current item has no MP3, the click is a no-op. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `content`, `contentIndex` | | **Events emitted** | `PressedDownload` | | **Requires** | a `` ancestor and the player UMD global loaded | # Feature Image Source: https://docs.beyondwords.io/components/html/feature-image Displays the feature image of the current content item. ```js FeatureImage.js theme={null} import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwFeatureImage extends BwPlayerConsumer { _img = null; _listener = null; constructor() { super(); this._img = document.createElement("img"); this._img.style.maxWidth = "100%"; this._img.style.objectFit = "contain"; } #render() { const item = this._player?.content?.[this._player.contentIndex]; if (!item?.imageUrl) { this._img.remove(); return; } if (this._img.getAttribute("src") !== item.imageUrl) this._img.setAttribute("src", item.imageUrl); this._img.setAttribute("alt", item.title ?? ""); if (!this._img.isConnected) this.appendChild(this._img); } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => this.#render()); this.#render(); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-feature-image", BwFeatureImage); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add feature-image --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add feature-image --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add feature-image --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add feature-image --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the image inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor, reads `content` and `contentIndex` from it, and renders the current item's `imageUrl` as an `` (using its `title` as the `alt` text), updating on every player event. It renders nothing when the item has no image, so it's safe to drop in unconditionally. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `content`, `contentIndex` | | **Events emitted** | None (display only) | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Introduction Source: https://docs.beyondwords.io/components/html/introduction Composable, minimal custom elements for the BeyondWords player. Own the code and style it your way. A set of framework-agnostic [custom elements](https://developer.mozilla.org/docs/Web/Web_Components) for building your own UI on top of the [BeyondWords player](https://github.com/beyondwords-io/player). They give you play/pause, seeking, playback rate and more as small building blocks you compose in plain HTML and style yourself. The player keeps doing the hard part: playback, analytics, ads, segments and lock-screen integration. This is not a packaged widget. The elements are distributed as a [shadcn registry](https://ui.shadcn.com/docs/registry), so **you own the code**: the CLI copies the source straight into your project, where you can restyle, rearrange or rewrite it. ```bash npm theme={null} npx @beyondwords/cli components add play-pause-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add play-pause-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add play-pause-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add play-pause-button --html ``` Here's the [Standard Player](/components/html/standard-player) example, running live, built entirely from these elements: Using React? Everything here is also available as [React components](/components/react/introduction). Use the React / HTML tabs at the top of the Components section. ## How it works The kit is three layers on top of one engine. Stop at whichever layer gives you the control you need: 1. **[Player Provider](/components/html/player-provider)**: `` mounts the player JavaScript SDK (loaded as the global `BeyondWords`), holds the instance, and shares it with the elements inside. This is the foundation everything else sits on. 2. **Components**: small, minimal custom elements like [Play Pause Button](/components/html/play-pause-button) and [Progress Bar](/components/html/progress-bar). You bring the styling. 3. **Examples**: complete, pre-assembled players you drop in and tweak, like the [Standard Player](/components/html/standard-player). Every element behaves the same way: * **It reads state from the player instance** by extending `BwPlayerConsumer`, which resolves the nearest provider and exposes the instance as `this._player`. Elements stay in sync by listening for player events and updating their own markup. * **It drives the player by emitting events** built with [`newEvent`](/components/html/new-event). This is the same path the standard player UI uses, so analytics, ads and segment logic stay correct. Simple state such as `playbackRate` can also be set directly on the instance. Every layer is copied into your codebase, so there's no black box and no version lock-in. The trade-off is that you maintain what you customize. ## Next steps Install the elements and build your first player. Browse the minimal building blocks. Restyle the elements to match your brand. Drop in a complete, pre-built player. # Minimal Player Source: https://docs.beyondwords.io/components/html/minimal-player A transparent, ultra-minimal player that expands from play + call to action into rate, seek-back, and time controls. ```html MinimalPlayer.html theme={null} ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add minimal-player --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add minimal-player --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add minimal-player --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add minimal-player --html ``` ## Usage Unlike the React block, there is no single `` element. Compose the custom elements inside a [``](/components/html/player-provider): ```html theme={null} ``` While stopped, only play and the "Listen to article" call to action are visible. Once playback starts, seek-back 10s, the speed dropdown (0.9x–2x), and current time appear instead. Every element resolves its nearest [``](/components/html/player-provider) ancestor and subscribes to player events; the buttons emit events built with [`newEvent`](/components/html/new-event). These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. # New Event Source: https://docs.beyondwords.io/components/html/new-event Utility for creating well-formed player event payloads. Stamps the common fields (`id`, `createdAt`, `status`, `initiatedBy`) onto a player event payload so every event you emit is well-formed. It's plain JS, identical across frameworks. ## Installation ```bash npm theme={null} npx @beyondwords/cli components add new-event --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add new-event --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add new-event --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add new-event --html ``` ## Usage ```js theme={null} import { newEvent } from "@/lib/newEvent"; player.onEvent( newEvent({ type: "PressedPlay", description: "The play button was pressed.", }), ); // => { id, createdAt, status: 'pending', initiatedBy: 'user', type, description } ``` # Next Seconds Button Source: https://docs.beyondwords.io/components/html/next-seconds-button Seeks forward by a fixed number of seconds. ```js NextSecondsButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { seekForwardSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwNextSecondsButton extends BwPlayerConsumer { _btn = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedSeekAhead", description: "The seek ahead button was pressed.", seconds: this._seconds, }), ); }); } get _seconds() { const seconds = Number(this.dataset.seconds); return Number.isFinite(seconds) && seconds > 0 ? seconds : 10; } connectedCallback() { super.connectedCallback(); this._btn.setAttribute("aria-label", `Skip forward ${this._seconds} seconds`); this._btn.innerHTML = seekForwardSvg(this._seconds, 24); this.appendChild(this._btn); } } customElements.define("bw-next-seconds-button", BwNextSecondsButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add next-seconds-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add next-seconds-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add next-seconds-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add next-seconds-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. On click, it emits a `PressedSeekAhead` event (built with [`newEvent`](/components/html/new-event)) carrying the number of `seconds` to seek. The `data-seconds` attribute controls both the jump distance and the label, and defaults to `10`. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------ | | **Attributes** | `data-seconds` (default `10`) | | **Player state read** | None | | **Events emitted** | `PressedSeekAhead` | | **Requires** | a `` ancestor and the player UMD global loaded | # Next Segment Button Source: https://docs.beyondwords.io/components/html/next-segment-button Jumps to the next segment and emits a user interaction event. ```js NextSegmentButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { nextSegmentSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwNextSegmentButton extends BwPlayerConsumer { _btn = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedNextSegment", description: "The next segment button was pressed.", }), ); }); } connectedCallback() { super.connectedCallback(); this._btn.setAttribute("aria-label", "Next segment"); this._btn.innerHTML = nextSegmentSvg(24); this.appendChild(this._btn); } } customElements.define("bw-next-segment-button", BwNextSegmentButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add next-segment-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add next-segment-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add next-segment-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add next-segment-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. On click, it emits a `PressedNextSegment` event built with [`newEvent`](/components/html/new-event). The player handles the event and moves playback to the next segment. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | None | | **Events emitted** | `PressedNextSegment` | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Next Track Button Source: https://docs.beyondwords.io/components/html/next-track-button Skips to the next track in a playlist. ```js NextTrackButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { nextTrackSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwNextTrackButton extends BwPlayerConsumer { _btn = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedNextTrack", description: "The next track button was pressed.", }), ); }); } connectedCallback() { super.connectedCallback(); this._btn.setAttribute("aria-label", "Next track"); this._btn.innerHTML = nextTrackSvg(24); this.appendChild(this._btn); } } customElements.define("bw-next-track-button", BwNextTrackButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add next-track-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add next-track-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add next-track-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add next-track-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. On click, it emits a `PressedNextTrack` event built with [`newEvent`](/components/html/new-event). It's intended for playlist sources, where the player moves to the next track. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | None | | **Events emitted** | `PressedNextTrack` | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Play Pause Button Source: https://docs.beyondwords.io/components/html/play-pause-button Toggles playback and emits user interaction events. ```js PlayPauseButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { pauseCircleSvg, playCircleSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwPlayPauseButton extends BwPlayerConsumer { _btn = null; _listener = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; const name = this._player.playbackState === "playing" ? "Pause" : "Play"; this._player.onEvent( newEvent({ type: `Pressed${name}`, description: `The ${name.toLowerCase()} button was pressed.`, }), ); }); } #render() { const isPlaying = this._player?.playbackState === "playing"; this._btn.setAttribute("aria-label", isPlaying ? "Pause audio" : "Play audio"); this._btn.innerHTML = isPlaying ? pauseCircleSvg(40) : playCircleSvg(40); } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => this.#render()); this.#render(); this.appendChild(this._btn); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-play-pause-button", BwPlayPauseButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add play-pause-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add play-pause-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add play-pause-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add play-pause-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` The button reads `playbackState` from the surrounding [``](/components/html/player-provider). On click, it emits a `PressedPlay` / `PressedPause` event built with [`newEvent`](/components/html/new-event). These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `playbackState` | | **Events emitted** | `PressedPlay`, `PressedPause` | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Playback Rate Button Source: https://docs.beyondwords.io/components/html/playback-rate-button Toggles the player's playback speed between 1x and 1.5x. ```js PlaybackRateButton.js theme={null} import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwPlaybackRateButton extends BwPlayerConsumer { _btn = null; _listener = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; this._player.playbackRate = this._player.playbackRate === 1 ? 1.5 : 1; }); } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => { this._btn.textContent = `${this._player.playbackRate}x`; }); this._btn.textContent = `${this._player?.playbackRate}x`; this.appendChild(this._btn); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-playback-rate-button", BwPlaybackRateButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add playback-rate-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add playback-rate-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add playback-rate-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add playback-rate-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor and reads `playbackRate` from it, updating on every player event. On click, it sets `player.playbackRate` directly on the instance, toggling between `1` and `1.5`. The current rate renders as the label (e.g. `1x`). Adjust the toggle logic to expose whatever speeds you want. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `playbackRate` | | **Events emitted** | None. Sets `player.playbackRate` directly | | **Requires** | a `` ancestor and the player UMD global loaded | # Player Provider Source: https://docs.beyondwords.io/components/html/player-provider Custom elements for providing and consuming the BeyondWords player instance. ## Installation ```bash npm theme={null} npx @beyondwords/cli components add player-provider --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add player-provider --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add player-provider --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add player-provider --html ``` ## Usage `` creates a `BeyondWords.Player` and exposes it to descendant elements. Extend `BwPlayerConsumer` to read it. ```html theme={null} ``` ### API * **``** creates the player from its `data-*` attributes (`data-project-id`, `data-content-id`, `data-source-id`, `data-playlist-id`, `data-player-style`, `data-show-user-interface`, plus inline `data-content`, `data-adverts`, `data-advert-index` and `data-analytics-consent`) and destroys it on disconnect. * **`BwPlayerConsumer`** is the base class. It walks up the DOM to find its provider and exposes the player via the `_player` getter and the widget mode via `_mode`. Throws if no `` ancestor exists. # Playlist Source: https://docs.beyondwords.io/components/html/playlist Render the player's content list and let users jump between items. ```js Playlist.js theme={null} import { newEvent } from "@/lib/newEvent"; import { nowPlayingMarkerSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwPlaylist extends BwPlayerConsumer { _list = null; _listener = null; _content = null; _contentIndex = null; constructor() { super(); this._list = document.createElement("ul"); this._list.style.listStyle = "none"; this._list.style.margin = "0"; this._list.style.padding = "0"; } #render() { const content = this._player?.content ?? []; const contentIndex = this._player?.contentIndex; if (content === this._content && contentIndex === this._contentIndex) return; this._content = content; this._contentIndex = contentIndex; this._list.replaceChildren( ...content.map((item, index) => { const btn = document.createElement("button"); btn.setAttribute("aria-current", contentIndex === index); btn.setAttribute("aria-label", `Play ${item.title}`); btn.style.display = "flex"; btn.style.alignItems = "center"; btn.style.gap = "0.5rem"; btn.style.width = "100%"; btn.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedPlaylistItem", description: "A playlist item was pressed.", index, }), ); }); const marker = document.createElement("span"); marker.style.display = "inline-flex"; marker.style.width = "1rem"; marker.style.justifyContent = "center"; if (contentIndex === index) { marker.innerHTML = nowPlayingMarkerSvg(16); } else { marker.textContent = index + 1; } const title = document.createElement("span"); title.textContent = item.title; btn.append(marker, title); const li = document.createElement("li"); li.appendChild(btn); return li; }), ); } connectedCallback() { super.connectedCallback(); this._listener = this._player?.addEventListener("", () => this.#render()); this.#render(); this.appendChild(this._list); } disconnectedCallback() { this._player?.removeEventListener("", this._listener); this._listener = null; super.disconnectedCallback(); } } customElements.define("bw-playlist", BwPlaylist); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add playlist --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add playlist --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add playlist --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add playlist --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the playlist inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor and maps over its `content`. It renders a button per item and marks the active one (`contentIndex`) with a now-playing icon. Pressing an item emits a `PressedPlaylistItem` event (built with [`newEvent`](/components/html/new-event)) carrying its `index`. The event is emitted rather than setting `player.contentIndex` directly. This lets the player handle ads and outros correctly when switching tracks. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | `content`, `contentIndex` | | **Events emitted** | `PressedPlaylistItem` (carries the item's `index`) | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Playlist Player Source: https://docs.beyondwords.io/components/html/playlist-player A playlist player example with a scrollable track list alongside playback controls. ```html PlaylistPlayer.html theme={null} ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add playlist-player --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add playlist-player --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add playlist-player --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add playlist-player --html ``` ## Usage Compose the playlist custom elements inside a [``](/components/html/player-provider). Import the modules (each calls `customElements.define`), then arrange the elements: ```html theme={null} ``` Unlike the React block, there is no single `` element. Pass `data-project-id` and `data-playlist-id` instead of a single `data-source-id`. `` (call-to-action + duration) shows while stopped, and `` (playback rate, previous/next track, time, and progress) shows once playback starts. `` shows or hides the scrollable `` of the playlist's items. Every element resolves its nearest [``](/components/html/player-provider) ancestor and subscribes to player events; the buttons emit events built with [`newEvent`](/components/html/new-event). These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. # Previous Seconds Button Source: https://docs.beyondwords.io/components/html/prev-seconds-button Seeks backward by a fixed number of seconds. ```js PrevSecondsButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { seekBackSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwPrevSecondsButton extends BwPlayerConsumer { _btn = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedSeekBack", description: "The seek backward button was pressed.", seconds: this._seconds, }), ); }); } get _seconds() { const seconds = Number(this.dataset.seconds); return Number.isFinite(seconds) && seconds > 0 ? seconds : 10; } connectedCallback() { super.connectedCallback(); this._btn.setAttribute("aria-label", `Skip back ${this._seconds} seconds`); this._btn.innerHTML = seekBackSvg(this._seconds, 24); this.appendChild(this._btn); } } customElements.define("bw-prev-seconds-button", BwPrevSecondsButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add prev-seconds-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add prev-seconds-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add prev-seconds-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add prev-seconds-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. On click, it emits a `PressedSeekBack` event (built with [`newEvent`](/components/html/new-event)) carrying the number of `seconds` to seek. The `data-seconds` attribute controls both the jump distance and the label, and defaults to `10`. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------ | | **Attributes** | `data-seconds` (default `10`) | | **Player state read** | None | | **Events emitted** | `PressedSeekBack` | | **Requires** | a `` ancestor and the player UMD global loaded | # Previous Segment Button Source: https://docs.beyondwords.io/components/html/prev-segment-button Jumps to the previous segment and emits a user interaction event. ```js PrevSegmentButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { previousSegmentSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwPrevSegmentButton extends BwPlayerConsumer { _btn = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedPrevSegment", description: "The previous segment button was pressed.", }), ); }); } connectedCallback() { super.connectedCallback(); this._btn.setAttribute("aria-label", "Previous segment"); this._btn.innerHTML = previousSegmentSvg(24); this.appendChild(this._btn); } } customElements.define("bw-prev-segment-button", BwPrevSegmentButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add prev-segment-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add prev-segment-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add prev-segment-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add prev-segment-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. On click, it emits a `PressedPrevSegment` event built with [`newEvent`](/components/html/new-event). The player handles the event and moves playback to the previous segment. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | None | | **Events emitted** | `PressedPrevSegment` | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Previous Track Button Source: https://docs.beyondwords.io/components/html/prev-track-button Skips to the previous track in a playlist. ```js PrevTrackButton.js theme={null} import { newEvent } from "@/lib/newEvent"; import { previousTrackSvg } from "@/registry/html/icons"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwPrevTrackButton extends BwPlayerConsumer { _btn = null; constructor() { super(); this._btn = document.createElement("button"); this._btn.addEventListener("click", () => { if (!this._player) return; this._player.onEvent( newEvent({ type: "PressedPrevTrack", description: "The previous track button was pressed.", }), ); }); } connectedCallback() { super.connectedCallback(); this._btn.setAttribute("aria-label", "Previous track"); this._btn.innerHTML = previousTrackSvg(24); this.appendChild(this._btn); } } customElements.define("bw-prev-track-button", BwPrevTrackButton); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add prev-track-button --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add prev-track-button --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add prev-track-button --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add prev-track-button --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the button inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. On click, it emits a `PressedPrevTrack` event built with [`newEvent`](/components/html/new-event). It's intended for playlist sources, where the player moves to the previous track. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | ------------------------------------------------------------------------------------------------------ | | **Attributes** | None | | **Player state read** | None | | **Events emitted** | `PressedPrevTrack` | | **Requires** | a [``](/components/html/player-provider) ancestor and the player UMD global loaded | # Progress Bar Source: https://docs.beyondwords.io/components/html/progress-bar A seekable progress bar that reflects playback and emits scrub events. ```js ProgressBar.js theme={null} import { newEvent } from "@/lib/newEvent"; import { BwPlayerConsumer } from "@/registry/html/PlayerProvider"; export class BwProgressBar extends BwPlayerConsumer { _background = null; _fill = null; _listener = null; _scrubbing = false; constructor() { super(); // A custom element's constructor must not touch the host (no attributes, // styles, or children) — only build detached nodes and bind handlers here. this._background = document.createElement("div"); this._background.style.position = "absolute"; this._background.style.inset = "0"; this._background.style.cursor = "pointer"; this._background.style.backgroundColor = "rgba(0, 0, 0, 0.8)"; this._background.style.opacity = "0.15"; this._fill = document.createElement("div"); this._fill.style.height = "0.5rem"; this._fill.style.width = "0%"; this._fill.style.borderRadius = "0.25rem"; this._fill.style.backgroundColor = "rgba(0, 0, 0, 0.8)"; this._fill.style.pointerEvents = "none"; this._onPress = this._onPress.bind(this); this._onMove = this._onMove.bind(this); this._onRelease = this._onRelease.bind(this); } connectedCallback() { super.connectedCallback(); this.style.position = "relative"; this.style.display = "block"; this.style.isolation = "isolate"; this.style.height = "0.5rem"; this.style.flexGrow = "1"; this.style.overflow = "hidden"; this.style.borderRadius = "0.25rem"; this.style.cursor = "pointer"; this.appendChild(this._background); this.appendChild(this._fill); this.addEventListener("mousedown", this._onPress); this.addEventListener("touchstart", this._onPress); // Track drags on the window so scrubbing continues outside the bar. window.addEventListener("mousemove", this._onMove); window.addEventListener("mouseup", this._onRelease); window.addEventListener("touchmove", this._onMove); window.addEventListener("touchend", this._onRelease); this._listener = this._player?.addEventListener("", () => this._update()); this._update(); } _ratio(event) { const clientX = event.clientX ?? event.touches?.[0]?.clientX ?? 0; const { x, width } = this.getBoundingClientRect(); return Math.max(0, Math.min(1, (clientX - x) / width)); } _onPress(event) { if (!this._player) return; this._scrubbing = true; this._player.onEvent( newEvent({ type: "PressedProgressBar", description: "The progress bar was pressed at some ratio.", initiatedBy: "user", ratio: this._ratio(event), }), ); } _onMove(event) { if (!this._scrubbing || !this._player) return; this._player.onEvent( newEvent({ type: "ScrubbedProgressBar", description: "The user pressed on the progress bar then dragged.", initiatedBy: "user", ratio: this._ratio(event), }), ); } _onRelease() { if (!this._scrubbing || !this._player) return; this._scrubbing = false; this._player.onEvent( newEvent({ type: "FinishedScrubbingProgressBar", description: "The user let go after scrubbing the progress bar.", initiatedBy: "user", }), ); } _update() { const currentTime = this._player?.currentTime ?? 0; const duration = this._player?.duration ?? 0; const progress = duration > 0 ? Math.max(0, Math.min(currentTime / duration, 1)) : 0; this._fill.style.width = `${progress * 100}%`; } disconnectedCallback() { this.removeEventListener("mousedown", this._onPress); this.removeEventListener("touchstart", this._onPress); window.removeEventListener("mousemove", this._onMove); window.removeEventListener("mouseup", this._onRelease); window.removeEventListener("touchmove", this._onMove); window.removeEventListener("touchend", this._onRelease); this._player?.removeEventListener("", this._listener); this._listener = null; this._scrubbing = false; super.disconnectedCallback(); } } customElements.define("bw-progress-bar", BwProgressBar); ``` ## Installation ```bash npm theme={null} npx @beyondwords/cli components add progress-bar --html ``` ```bash pnpm theme={null} pnpm dlx @beyondwords/cli components add progress-bar --html ``` ```bash yarn theme={null} yarn dlx @beyondwords/cli components add progress-bar --html ``` ```bash bun theme={null} bunx --bun @beyondwords/cli components add progress-bar --html ``` ## Usage Import the modules (each calls `customElements.define`), then use the elements. Nest the bar inside a ``. ```html theme={null} ``` `` finds its nearest [``](/components/html/player-provider) ancestor. The bar fills to `currentTime / duration`. Pressing and dragging emits a sequence of events built with [`newEvent`](/components/html/new-event). Each one carries the pressed `ratio` (0–1): * **`PressedProgressBar`**: on press, to seek to a position. * **`ScrubbedProgressBar`**: while dragging. * **`FinishedScrubbingProgressBar`**: on release. Drag tracking is attached to `window` (mouse and touch), so scrubbing keeps working even when the pointer leaves the bar. The element grows to fill its container (`flex-grow: 1`). Place it in a flex row alongside the other controls. These components expect the BeyondWords player to be available as a global `BeyondWords` (the player UMD script). Load it before the provider connects. ## Reference | | | | --------------------- | --------------------------------------------------------------------------------------------------------- | | **Attributes** | None | | **Player state read** | `currentTime`, `duration` | | **Events emitted** | `PressedProgressBar`, `ScrubbedProgressBar`, `FinishedScrubbingProgressBar` (each carries a `ratio`, 0–1) | | **Requires** | a `` ancestor and the player UMD global loaded | # Setup Source: https://docs.beyondwords.io/components/html/setup Install the custom elements with the BeyondWords CLI and build your first player. The elements ship as a shadcn registry. Add them with the CLI and the source is copied into your project. They also need the BeyondWords player script on the page. ## Prerequisites * A project where you can run `npx` commands and serve ES modules. The CLI copies the element source into your project. * A BeyondWords **project ID** and a **source ID** (or content ID). The examples below use a public demo project. ## Load the player script The elements expect the BeyondWords player as a global `BeyondWords`. Add the player UMD script to your page so it loads before the provider connects: ```html theme={null} ``` ## Add a component Add the [Play Pause Button](/components/html/play-pause-button). No configuration is needed. The `--html` flag selects the HTML registry, and the CLI resolves dependencies automatically, so this also pulls in [Player Provider](/components/html/player-provider) and [New Event](/components/html/new-event). ```bash theme={null} npx @beyondwords/cli components add play-pause-button --html ``` The CLI copies the files to `components/beyondwords/` by default (it asks first), or respects your aliases if the project has a `components.json`. The examples in these docs import from `@/registry/html/`. Adjust the paths to wherever the files landed, and serve them through a bundler or an import map so the browser can resolve them. ## Build your first player Each module calls `customElements.define`, so importing it registers the tag. Nest your controls inside a ``, which mounts the player and shares it with the elements inside. `data-show-user-interface="false"` hides the player's built-in UI so yours is the only one on the page. ```html theme={null} ``` Here's that button running live: It looks plain because the elements are minimal by design; [Theming](/components/html/theming) shows how to make it yours. Add more controls the same way: import the module, nest the element inside the provider. To build your own controls, extend `BwPlayerConsumer`, which resolves the player instance from the nearest ``. See [Player Provider](/components/html/player-provider) for the pattern, and [New Event](/components/html/new-event) for emitting player events. ## Using the shadcn CLI instead (optional) The registry also works with the shadcn CLI directly. Add components by full URL: ```bash theme={null} npx shadcn add https://ui.beyondwords.io/r/html/play-pause-button.json ``` Or register the registry namespace once in your `components.json`, then use short names: ```json components.json theme={null} { "registries": { "@beyondwords-html": "https://ui.beyondwords.io/r/html/{name}.json" } } ``` ```bash theme={null} npx shadcn add @beyondwords-html/play-pause-button ``` ## Next steps Style the elements to match your brand. Provide and consume the player. Start from a complete, pre-built player. Fix common setup issues. # Standard Player Source: https://docs.beyondwords.io/components/html/standard-player A full-featured player block with playback controls, a call to action, and duration. ```html StandardPlayer.html theme={null} ``` ## Try it An editable example running on [StackBlitz](https://stackblitz.com/edit/html-standard-player?file=index.html): ``` * The `