API Reference
Integrate PodcastFly data into your own apps. All endpoints return JSON.
Overview
Base URL
https://your-domain.com/apiAuthentication
Protected endpoints require a valid session cookie obtained via POST /api/auth/signin (credentials) or OAuth. Pass the Cookie header with your session token in server-to-server requests. For browser-based integrations, the cookie is set automatically after login.
Response format
All responses are application/json. Error responses follow the shape:
{ "error": "Human-readable message" }HTTP Status Codes
200OK — Success201Created — Resource created400Bad Request — Invalid parameters401Unauthorized — Not authenticated403Forbidden — Insufficient role404Not Found409Conflict — Duplicate resource500Internal Server ErrorAuthentication
PodcastFly uses session-based auth via NextAuth.js. To authenticate in server-to-server contexts, POST credentials to obtain a session cookie, then include it in subsequent requests.
/api/auth/registerRegister a new user account
Creates a new user with the LISTENER role. Returns the created user (no password).
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| string | ✓ | Valid email address | |
| password | string | ✓ | Minimum 8 characters |
| name | string | – | Display name |
Example Request
POST /api/auth/register
Content-Type: application/json
{
"email": "alice@example.com",
"password": "securepassword",
"name": "Alice"
}Example Response
// 201 Created
{
"id": "cuid_abc123",
"email": "alice@example.com",
"name": "Alice",
"role": "LISTENER"
}Podcasts
/api/podcastsList podcasts
Returns a paginated list of podcasts. Defaults to APPROVED podcasts only.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| search | string | – | Full-text search on title, description, author |
| limit | number | – | Max results (default: 20) |
| offset | number | – | Pagination offset (default: 0) |
| status | string | – | APPROVED | PENDING | REJECTED | PAUSED (default: APPROVED) |
| siteId | string | – | Filter by site (multi-tenant deployments) |
Example Response
// 200 OK — array of podcast objects
[
{
"id": "cuid_podcast1",
"title": "The Dev Show",
"slug": "the-dev-show",
"description": "Weekly software engineering discussions",
"imageUrl": "https://example.com/artwork.jpg",
"rssUrl": "https://example.com/feed.xml",
"author": "Jane Smith",
"language": "en",
"status": "APPROVED",
"episodeCount": 42,
"subscriberCount": 128,
"categories": [
{ "id": "cat1", "name": "Technology", "slug": "technology", "color": "#3B82F6" }
],
"createdAt": "2024-01-15T10:00:00.000Z"
}
]/api/podcasts/:idGet podcast by ID
Returns a single podcast with categories and counts. Returns 404 if not found.
Example Response
// 200 OK
{
"id": "cuid_podcast1",
"title": "The Dev Show",
"slug": "the-dev-show",
"description": "Weekly software engineering discussions",
"imageUrl": "https://example.com/artwork.jpg",
"rssUrl": "https://example.com/feed.xml",
"author": "Jane Smith",
"language": "en",
"status": "APPROVED",
"categories": [...],
"_count": { "episodes": 42, "subscriptions": 128 }
}/api/podcasts/:id/episodesList episodes for a podcast
Returns episodes sorted by publishedAt descending.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| limit | number | – | Max results (default: 30) |
| offset | number | – | Pagination offset (default: 0) |
Example Response
// 200 OK — array of episode objects
[
{
"id": "cuid_ep1",
"podcastId": "cuid_podcast1",
"title": "Episode 42: Building with AI",
"slug": "episode-42-building-with-ai-1705312800000",
"description": "<p>This week we discuss...</p>",
"audioUrl": "https://cdn.example.com/ep42.mp3",
"duration": 3720,
"imageUrl": null,
"publishedAt": "2024-01-15T08:00:00.000Z",
"guid": "https://example.com/ep42"
}
]Episodes
/api/episodesGet episode by podcast and episode slug
Fetch a single episode using its URL-friendly slugs. Also returns the parent podcast metadata.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| podcastSlug | string | ✓ | Podcast slug (e.g. the-dev-show) |
| episodeSlug | string | ✓ | Episode slug |
Example Response
// 200 OK
{
"id": "cuid_ep1",
"podcastId": "cuid_podcast1",
"podcastSlug": "the-dev-show",
"podcastTitle": "The Dev Show",
"podcastImageUrl": "https://example.com/artwork.jpg",
"title": "Episode 42: Building with AI",
"slug": "episode-42-building-with-ai-1705312800000",
"description": "<p>This week we discuss...</p>",
"audioUrl": "https://cdn.example.com/ep42.mp3",
"duration": 3720,
"publishedAt": "2024-01-15T08:00:00.000Z",
"imageUrl": null,
"favoriteCount": 14,
"isFavorited": false
}/api/episodes/:idGet episode by ID
Returns a single episode by its database ID.
Example Response
// 200 OK
{
"id": "cuid_ep1",
"podcastId": "cuid_podcast1",
"title": "Episode 42: Building with AI",
"slug": "episode-42-building-with-ai-1705312800000",
"description": "<p>Full HTML description</p>",
"audioUrl": "https://cdn.example.com/ep42.mp3",
"duration": 3720,
"publishedAt": "2024-01-15T08:00:00.000Z",
"guid": "https://example.com/ep42"
}Favorites
All favorites endpoints require an authenticated session.
/api/favorites🔒 Auth requiredList the current user's favorite episodes
Example Response
// 200 OK — array of episode objects (same shape as episode list)
[
{
"id": "cuid_ep1",
"title": "Episode 42: Building with AI",
"slug": "...",
"audioUrl": "...",
"duration": 3720,
"podcast": {
"slug": "the-dev-show",
"title": "The Dev Show",
"imageUrl": "..."
}
}
]/api/favorites🔒 Auth requiredAdd an episode to favorites
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| episodeId | string | ✓ | Episode ID to favourite |
Example Request
POST /api/favorites
Content-Type: application/json
{ "episodeId": "cuid_ep1" }Example Response
// 201 Created
{
"id": "cuid_fav1",
"userId": "cuid_user1",
"episodeId": "cuid_ep1",
"createdAt": "2024-02-01T12:00:00.000Z"
}/api/favorites🔒 Auth requiredRemove an episode from favorites
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| episodeId | string | ✓ | Episode ID to unfavourite |
Example Request
DELETE /api/favorites
Content-Type: application/json
{ "episodeId": "cuid_ep1" }Example Response
// 200 OK
{ "ok": true }Subscriptions
Subscribe to podcasts to receive notifications when new episodes are published.
/api/subscriptions🔒 Auth requiredList the current user's podcast subscriptions
Example Response
// 200 OK — array of podcast objects
[
{
"id": "cuid_podcast1",
"title": "The Dev Show",
"slug": "the-dev-show",
"imageUrl": "...",
"_count": { "episodes": 42 }
}
]/api/subscriptions🔒 Auth requiredSubscribe to a podcast
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| podcastId | string | ✓ | Podcast ID to subscribe to |
Example Request
POST /api/subscriptions
Content-Type: application/json
{ "podcastId": "cuid_podcast1" }Example Response
// 201 Created
{
"id": "cuid_sub1",
"userId": "cuid_user1",
"podcastId": "cuid_podcast1",
"createdAt": "2024-02-01T12:00:00.000Z"
}/api/subscriptions🔒 Auth requiredUnsubscribe from a podcast
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| podcastId | string | ✓ | Podcast ID to unsubscribe from |
Example Request
DELETE /api/subscriptions
Content-Type: application/json
{ "podcastId": "cuid_podcast1" }Example Response
// 200 OK
{ "ok": true }Listening List
A personal queue of episodes the user wants to listen to later. Items are ordered by position.
/api/listening-list🔒 Auth requiredGet the current user's listening list
Example Response
// 200 OK — array of episode objects ordered by position
[
{
"id": "cuid_ep2",
"title": "Episode 43: TypeScript Deep Dive",
"slug": "...",
"audioUrl": "...",
"duration": 4200,
"podcast": {
"slug": "the-dev-show",
"title": "The Dev Show",
"imageUrl": "..."
}
}
]/api/listening-list🔒 Auth requiredAdd an episode to the listening list
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| episodeId | string | ✓ | Episode ID to add |
Example Request
POST /api/listening-list
Content-Type: application/json
{ "episodeId": "cuid_ep2" }Example Response
// 201 Created
{
"id": "cuid_item1",
"userId": "cuid_user1",
"episodeId": "cuid_ep2",
"position": 3,
"createdAt": "2024-02-01T12:00:00.000Z"
}/api/listening-list🔒 Auth requiredRemove an episode from the listening list
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| episodeId | string | ✓ | Episode ID to remove |
Example Request
DELETE /api/listening-list
Content-Type: application/json
{ "episodeId": "cuid_ep2" }Example Response
// 200 OK
{ "ok": true }Ratings
Users can rate both podcasts and individual episodes on a 1–5 scale. Only one rating per user per target is stored (upsert behaviour).
/api/ratings🔒 Auth requiredSubmit or update a rating
Provide either podcastId or episodeId (not both). The rating is upserted — submitting again updates the existing rating.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| value | number | ✓ | Integer from 1 to 5 |
| podcastId | string | – | Rate a podcast (provide this OR episodeId) |
| episodeId | string | – | Rate an episode (provide this OR podcastId) |
Example Request
POST /api/ratings
Content-Type: application/json
{
"podcastId": "cuid_podcast1",
"value": 5
}Example Response
// 200 OK
{
"id": "cuid_rating1",
"userId": "cuid_user1",
"podcastId": "cuid_podcast1",
"episodeId": null,
"value": 5,
"createdAt": "2024-02-01T12:00:00.000Z"
}Comments
Threaded comments on episodes and podcasts. Top-level comments include their replies nested inside.
/api/commentsList comments for an episode or podcast
Returns top-level comments with nested replies. Provide either episodeId or podcastId.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| episodeId | string | – | Fetch comments for this episode |
| podcastId | string | – | Fetch comments for this podcast |
Example Response
// 200 OK
[
{
"id": "cuid_comment1",
"content": "Great episode!",
"createdAt": "2024-02-01T12:00:00.000Z",
"user": { "id": "cuid_user1", "name": "Alice", "image": null },
"_count": { "likes": 3 },
"replies": [
{
"id": "cuid_comment2",
"content": "Totally agree!",
"user": { "id": "cuid_user2", "name": "Bob", "image": null },
"_count": { "likes": 1 }
}
]
}
]/api/comments🔒 Auth requiredPost a comment
Post a top-level comment or reply. Provide parentId to reply to an existing comment.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| content | string | ✓ | Comment text |
| episodeId | string | – | Target episode (provide this OR podcastId) |
| podcastId | string | – | Target podcast (provide this OR episodeId) |
| parentId | string | – | Parent comment ID for threaded replies |
Example Request
POST /api/comments
Content-Type: application/json
{
"episodeId": "cuid_ep1",
"content": "This was so insightful, thanks!"
}Example Response
// 201 Created
{
"id": "cuid_comment3",
"content": "This was so insightful, thanks!",
"episodeId": "cuid_ep1",
"podcastId": null,
"parentId": null,
"createdAt": "2024-02-01T12:00:00.000Z",
"user": { "id": "cuid_user1", "name": "Alice", "image": null }
}Notifications
Notification types include NEW_EPISODE, COMMENT_REPLY, PODCAST_APPROVED, and PODCAST_REJECTED.
/api/notifications🔒 Auth requiredList the current user's notifications
Returns up to 50 notifications, newest first.
Example Response
// 200 OK
[
{
"id": "cuid_notif1",
"type": "NEW_EPISODE",
"title": "New episode on The Dev Show",
"message": "Episode 43: TypeScript Deep Dive is now available",
"link": "/podcast/the-dev-show/episodes/episode-43-typescript-deep-dive",
"read": false,
"createdAt": "2024-02-01T09:00:00.000Z"
}
]/api/notifications🔒 Auth requiredMark notifications as read
Pass an array of notification IDs to mark them as read. Only marks notifications belonging to the authenticated user.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| ids | string[] | ✓ | Array of notification IDs to mark as read |
Example Request
PATCH /api/notifications
Content-Type: application/json
{ "ids": ["cuid_notif1", "cuid_notif2"] }Example Response
// 200 OK
{ "ok": true }Listening History
Tracks playback position per episode. Used to resume listening and mark episodes as completed. An episode is considered completed when ≤ 3 minutes remain.
/api/history🔒 Auth requiredGet listening history
Without parameters: returns the full history (last 100 entries). With episodeId: returns the saved position for that single episode.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| episodeId | string | – | If provided, returns position data for just this episode |
Example Response
// 200 OK — without episodeId (full history)
[
{
"id": "cuid_hist1",
"userId": "cuid_user1",
"episodeId": "cuid_ep1",
"position": 1840,
"duration": 3720,
"completed": false,
"updatedAt": "2024-02-01T14:30:00.000Z",
"episode": {
"id": "cuid_ep1",
"title": "Episode 42: Building with AI",
"slug": "...",
"imageUrl": null,
"duration": 3720,
"publishedAt": "...",
"podcast": { "title": "The Dev Show", "slug": "the-dev-show", "imageUrl": "..." }
}
}
]
// With ?episodeId=cuid_ep1
{ "position": 1840, "duration": 3720, "completed": false }
// If no history exists for that episode
null/api/history🔒 Auth requiredSave playback position
Upserts the listening history entry for the given episode. Call this periodically during playback and on pause.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| episodeId | string | ✓ | Episode being played |
| position | number | ✓ | Current playback position in seconds |
| duration | number | – | Total episode duration in seconds |
| completed | boolean | – | Set true when ≤ 3 min remain |
Example Request
POST /api/history
Content-Type: application/json
{
"episodeId": "cuid_ep1",
"position": 1840,
"duration": 3720,
"completed": false
}Example Response
// 200 OK
{
"id": "cuid_hist1",
"position": 1840,
"completed": false
}Creator
These endpoints require CREATOR or ADMIN role. A user's role can be upgraded by an admin.
/api/creator/submit-rss🔒 CREATOR or ADMINPreview an RSS feed
Parses the RSS feed at the given URL and returns podcast metadata + first 5 episodes. Use this to show a preview before submitting.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| rssUrl | string | ✓ | Publicly accessible RSS feed URL |
Example Response
// 200 OK
{
"podcastMeta": {
"title": "The Dev Show",
"description": "Weekly software engineering discussions",
"imageUrl": "https://example.com/artwork.jpg",
"author": "Jane Smith",
"language": "en"
},
"episodes": [
{
"title": "Episode 43: TypeScript Deep Dive",
"description": "<p>...</p>",
"audioUrl": "https://cdn.example.com/ep43.mp3",
"duration": 4200,
"publishedAt": "2024-02-05T08:00:00.000Z",
"guid": "https://example.com/ep43"
}
]
}/api/creator/submit-rss🔒 CREATOR or ADMINSubmit a podcast for review
Parses the RSS feed, creates the podcast with PENDING status, and queues it for admin review. Returns 409 if the RSS URL is already registered.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| rssUrl | string | ✓ | Publicly accessible RSS feed URL |
Example Request
POST /api/creator/submit-rss
Content-Type: application/json
{ "rssUrl": "https://example.com/feed.xml" }Example Response
// 201 Created
{
"id": "cuid_podcast2",
"title": "The Dev Show",
"slug": "the-dev-show",
"rssUrl": "https://example.com/feed.xml",
"status": "PENDING",
"createdAt": "2024-02-01T12:00:00.000Z"
}Usage Notes
- This API is intended for personal integrations and approved third-party apps.
- Do not cache user-specific endpoints (favorites, history, etc.) for more than a few seconds.
- Audio files are served directly from the podcast's original RSS feed — PodcastFly does not proxy or re-host audio.
- For high-volume integrations, contact us to discuss rate limits and API key authentication.