Login
REST API v1

API Reference

Integrate PodcastFly data into your own apps. All endpoints return JSON.

Overview

Base URL

https://your-domain.com/api

Authentication

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 — Success
201Created — Resource created
400Bad Request — Invalid parameters
401Unauthorized — Not authenticated
403Forbidden — Insufficient role
404Not Found
409Conflict — Duplicate resource
500Internal Server Error

Authentication

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.

POST/api/auth/register

Register a new user account

Creates a new user with the LISTENER role. Returns the created user (no password).

Request Body

NameTypeRequiredDescription
emailstringValid email address
passwordstringMinimum 8 characters
namestringDisplay 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

GET/api/podcasts

List podcasts

Returns a paginated list of podcasts. Defaults to APPROVED podcasts only.

Query Parameters

NameTypeRequiredDescription
searchstringFull-text search on title, description, author
limitnumberMax results (default: 20)
offsetnumberPagination offset (default: 0)
statusstringAPPROVED | PENDING | REJECTED | PAUSED (default: APPROVED)
siteIdstringFilter 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"
  }
]
GET/api/podcasts/:id

Get 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 }
}
GET/api/podcasts/:id/episodes

List episodes for a podcast

Returns episodes sorted by publishedAt descending.

Query Parameters

NameTypeRequiredDescription
limitnumberMax results (default: 30)
offsetnumberPagination 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

GET/api/episodes

Get episode by podcast and episode slug

Fetch a single episode using its URL-friendly slugs. Also returns the parent podcast metadata.

Query Parameters

NameTypeRequiredDescription
podcastSlugstringPodcast slug (e.g. the-dev-show)
episodeSlugstringEpisode 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
}
GET/api/episodes/:id

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

GET/api/favorites🔒 Auth required

List 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": "..."
    }
  }
]
POST/api/favorites🔒 Auth required

Add an episode to favorites

Request Body

NameTypeRequiredDescription
episodeIdstringEpisode 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"
}
DELETE/api/favorites🔒 Auth required

Remove an episode from favorites

Request Body

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

GET/api/subscriptions🔒 Auth required

List 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 }
  }
]
POST/api/subscriptions🔒 Auth required

Subscribe to a podcast

Request Body

NameTypeRequiredDescription
podcastIdstringPodcast 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"
}
DELETE/api/subscriptions🔒 Auth required

Unsubscribe from a podcast

Request Body

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

GET/api/listening-list🔒 Auth required

Get 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": "..."
    }
  }
]
POST/api/listening-list🔒 Auth required

Add an episode to the listening list

Request Body

NameTypeRequiredDescription
episodeIdstringEpisode 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"
}
DELETE/api/listening-list🔒 Auth required

Remove an episode from the listening list

Request Body

NameTypeRequiredDescription
episodeIdstringEpisode 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).

POST/api/ratings🔒 Auth required

Submit or update a rating

Provide either podcastId or episodeId (not both). The rating is upserted — submitting again updates the existing rating.

Request Body

NameTypeRequiredDescription
valuenumberInteger from 1 to 5
podcastIdstringRate a podcast (provide this OR episodeId)
episodeIdstringRate 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.

GET/api/comments

List comments for an episode or podcast

Returns top-level comments with nested replies. Provide either episodeId or podcastId.

Query Parameters

NameTypeRequiredDescription
episodeIdstringFetch comments for this episode
podcastIdstringFetch 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 }
      }
    ]
  }
]
POST/api/comments🔒 Auth required

Post a comment

Post a top-level comment or reply. Provide parentId to reply to an existing comment.

Request Body

NameTypeRequiredDescription
contentstringComment text
episodeIdstringTarget episode (provide this OR podcastId)
podcastIdstringTarget podcast (provide this OR episodeId)
parentIdstringParent 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.

GET/api/notifications🔒 Auth required

List 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"
  }
]
PATCH/api/notifications🔒 Auth required

Mark notifications as read

Pass an array of notification IDs to mark them as read. Only marks notifications belonging to the authenticated user.

Request Body

NameTypeRequiredDescription
idsstring[]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.

GET/api/history🔒 Auth required

Get listening history

Without parameters: returns the full history (last 100 entries). With episodeId: returns the saved position for that single episode.

Query Parameters

NameTypeRequiredDescription
episodeIdstringIf 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
POST/api/history🔒 Auth required

Save playback position

Upserts the listening history entry for the given episode. Call this periodically during playback and on pause.

Request Body

NameTypeRequiredDescription
episodeIdstringEpisode being played
positionnumberCurrent playback position in seconds
durationnumberTotal episode duration in seconds
completedbooleanSet 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.

GET/api/creator/submit-rss🔒 CREATOR or ADMIN

Preview 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

NameTypeRequiredDescription
rssUrlstringPublicly 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"
    }
  ]
}
POST/api/creator/submit-rss🔒 CREATOR or ADMIN

Submit 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

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