npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

podscan

v1.0.0

Published

Lightweight, zero-dependency TypeScript SDK for the Podscan REST API

Readme

podscan

Lightweight, zero-dependency TypeScript SDK for the Podscan REST API. Optimized for AWS Lambda and serverless environments.

  • Zero runtime dependencies -- uses native fetch (Node 18+)
  • Dual format -- ESM and CommonJS
  • Fully typed -- complete TypeScript definitions including episode metadata (hosts, guests, speakers)
  • Auto-pagination -- searchAll() async iterators walk all pages automatically
  • Time period helpers -- periods.thisWeek(), periods.lastMonth(), etc.
  • Delta sync -- checkpoint-based tracking for incremental data pulls
  • Tiny -- under 10 KB minified (ESM)

Install

npm install podscan

Quick Start

import { PodscanClient } from 'podscan';

const client = new PodscanClient({
  apiKey: process.env.PODSCAN_API_KEY!,
});

// Search episodes
const results = await client.episodes.search({
  query: 'AI marketing',
  language: 'en',
  per_page: 10,
});

console.log(`Found ${results.pagination.total} episodes`);

for (const episode of results.episodes) {
  console.log(`${episode.episode_title} — ${episode.podcast?.podcast_name}`);
}

Lambda Usage

The SDK uses native fetch with no external dependencies, so cold starts are fast and bundle size is minimal.

import { PodscanClient } from 'podscan';

const client = new PodscanClient({
  apiKey: process.env.PODSCAN_API_KEY!,
  timeout: 10_000, // tighter timeout for Lambda
});

export const handler = async (event: any) => {
  const results = await client.episodes.search({
    query: event.queryStringParameters?.q ?? 'tech',
  });

  return {
    statusCode: 200,
    body: JSON.stringify(results.episodes),
  };
};

Configuration

const client = new PodscanClient({
  apiKey: 'your-api-key',   // Required. Bearer token for Podscan API.
  baseUrl: 'https://...',   // Optional. Override API base URL.
  timeout: 30_000,          // Optional. Request timeout in ms (default: 30000).
});

API Reference

All methods return typed promises. Parameters mirror the Podscan API docs.

client.episodes

| Method | Description | |---|---| | search(params) | Full-text search across episode transcripts, titles, and descriptions | | get(params) | Get detailed info about a specific episode | | getRecent(params?) | Get the most recently published episodes | | getByPodcast(params) | List all episodes for a specific podcast |

// Search episodes with filters
const results = await client.episodes.search({
  query: 'machine learning',
  language: 'en',
  has_guests: true,
  since: '2026-01-01',
  order_by: 'relevance',
  per_page: 25,
});

// Get episode details with transcript
const episode = await client.episodes.get({
  episode_id: 'ep_m9v2x7kq4pn8rjsw',
  include_transcript: true,
  include_entities: true,
});

// Get recent episodes
const recent = await client.episodes.getRecent({ limit: 10, language: 'en' });

// List episodes for a podcast
const podcastEpisodes = await client.episodes.getByPodcast({
  podcast_id: 'pd_ka86x53ynan9wgdv',
  order_by: 'posted_at',
  per_page: 50,
});

client.podcasts

| Method | Description | |---|---| | search(params) | Search podcasts by name, topic, or characteristics | | get(params) | Get detailed info about a specific podcast |

// Search podcasts
const podcasts = await client.podcasts.search({
  query: 'business',
  has_guests: true,
  min_episode_count: 50,
  order_by: 'audience_size',
});

// Get podcast details
const podcast = await client.podcasts.get({
  podcast_id: 'pd_ka86x53ynan9wgdv',
  include_episodes: true,
  episode_limit: 5,
});

client.alerts

| Method | Description | |---|---| | list(params?) | List your team's content monitoring alerts | | getMentions(params) | Get mentions found by a specific alert | | create(params) | Create a new content monitoring alert |

// List alerts
const alerts = await client.alerts.list({ enabled_only: true });

// Get mentions for an alert
const mentions = await client.alerts.getMentions({
  alert_id: 'al_h3f5g8k2m7n4p9q6',
  since: '2026-02-01',
});

// Create an alert with filter expressions
const alert = await client.alerts.create({
  name: 'Brand Monitor',
  filters: '"Acme Corp"\nAcme AND (product OR service)',
  webhook_url: 'https://example.com/webhook',
  webhook_active: true,
});

client.topics

| Method | Description | |---|---| | search(params) | Discover topics discussed across podcasts | | get(params) | Get detailed info about a specific topic | | getEpisodes(params) | Get episodes where a topic was mentioned | | getTrending(params?) | Get currently trending topics |

// Search topics
const topics = await client.topics.search({
  query: 'cryptocurrency',
  min_episodes: 100,
});

// Get topic with history
const topic = await client.topics.get({
  topic_id: 'tp_z8x6c4v2b0n9m7k5',
  with_history: true,
});

// Get episodes for a topic
const topicEpisodes = await client.topics.getEpisodes({
  topic_id: 'tp_z8x6c4v2b0n9m7k5',
  podcast_audience_min: 10000,
  per_page: 25,
});

// Get trending topics
const trending = await client.topics.getTrending({ period: '7d', limit: 20 });

client.entities

| Method | Description | |---|---| | search(params) | Search for people and organizations mentioned in podcasts | | get(params) | Get detailed info about a person or organization | | getAppearances(params) | Get all podcast appearances for an entity |

// Search entities
const entities = await client.entities.search({
  query: 'Elon',
  entity_type: 'person',
  min_appearances: 100,
});

// Get entity with recent appearances
const entity = await client.entities.get({
  entity_id: 'en_p4o2i8u6y3t1r5e9',
  with_appearances: true,
  appearances_limit: 10,
});

// Get all appearances filtered by role
const appearances = await client.entities.getAppearances({
  entity_id: 'en_p4o2i8u6y3t1r5e9',
  role: 'guest',
  order_dir: 'desc',
});

client.lists

| Method | Description | |---|---| | list(params?) | Get all lists/collections for your team | | getItems(params) | Get contents of a specific list | | addItems(params) | Add items to a list (podcasts, episodes, entities, topics) |

// List all collections
const lists = await client.lists.list();

// Get list items filtered by type
const items = await client.lists.getItems({
  list_id: 'cl_q9w3e5r7t1y4u8i2',
  item_type: 'podcasts',
});

// Add items to a list
const result = await client.lists.addItems({
  list_id: 'cl_q9w3e5r7t1y4u8i2',
  item_ids: 'pd_ka86x53ynan9wgdv,ep_m9v2x7kq4pn8rjsw',
});

client.publishers

| Method | Description | |---|---| | get(params) | Get publisher info with their podcast portfolio |

const publisher = await client.publishers.get({
  publisher_id: 'pb_l7k5j3h1g9f6d4s2',
  include_podcasts: true,
  podcast_limit: 20,
});

Time Periods

The periods helper computes date ranges you can spread into any search call:

import { PodscanClient, periods } from 'podscan';

const client = new PodscanClient({ apiKey: process.env.PODSCAN_API_KEY! });

// This week's AI episodes
const results = await client.episodes.search({
  query: 'AI',
  ...periods.thisWeek(),
  per_page: 50,
});

Available presets:

| Method | Range | |---|---| | periods.today() | Midnight UTC today to now | | periods.yesterday() | Yesterday midnight to today midnight | | periods.last24Hours() | Rolling 24-hour window | | periods.thisWeek() | Monday 00:00 UTC to now | | periods.lastWeek() | Previous Monday to this Monday | | periods.thisMonth() | 1st of month to now | | periods.lastMonth() | 1st of last month to 1st of this month | | periods.lastNDays(n) | Rolling N-day window | | periods.lastNHours(n) | Rolling N-hour window | | periods.since(date) | Everything after a Date or ISO string |

All dates are UTC ISO 8601 strings. Each returns { since, before? }.

Auto-Pagination

Every paginated resource has a searchAll() method that returns an async iterator, walking all pages automatically:

// Iterate every matching episode across all pages
for await (const episode of client.episodes.searchAll({
  query: 'artificial intelligence',
  ...periods.thisWeek(),
  has_guests: true,
})) {
  console.log(episode.episode_title);
  console.log('  Guests:', episode.metadata?.guests.map(g => g.guest_name).join(', '));
}

Available auto-paginating methods:

| Resource | Method | Yields | |---|---|---| | client.episodes | searchAll(params) | Episode | | client.episodes | getByPodcastAll(params) | Episode | | client.podcasts | searchAll(params) | Podcast | | client.topics | searchAll(params) | TopicSummary | | client.topics | getEpisodesAll(params) | Episode | | client.entities | searchAll(params) | Entity |

Delta Sync (Checkpoints)

Track what you've already pulled so the next run only fetches new content:

import { PodscanClient, periods } from 'podscan';

const client = new PodscanClient({ apiKey: process.env.PODSCAN_API_KEY! });

// First run: pull this week's episodes
const paginator = client.episodes.searchAll({
  query: 'AI',
  ...periods.thisWeek(),
});

for await (const episode of paginator) {
  await saveToDatabase(episode);
}

// Save checkpoint
const checkpoint = paginator.checkpoint();
// { lastSeenAt: '2026-02-16T14:30:00Z', lastSeenId: 'ep_abc123', totalSeen: 142 }
await saveCheckpoint(checkpoint);

// Next run: only get new episodes since last checkpoint
const lastCheckpoint = await loadCheckpoint();
const newEpisodes = client.episodes.searchAll({
  query: 'AI',
  since: lastCheckpoint.lastSeenAt,
});

for await (const episode of newEpisodes) {
  await saveToDatabase(episode);
}

Transcripts and Guest Data

Every episode includes full transcript and structured metadata with host/guest/speaker info:

const results = await client.episodes.search({
  query: 'machine learning',
  has_guests: true,
  ...periods.thisWeek(),
  per_page: 10,
});

for (const ep of results.episodes) {
  // Full transcript with timestamps and speaker labels
  console.log(ep.episode_transcript);
  // "[00:00:08] [SPEAKER_01] Welcome to the show..."

  // Structured metadata
  const meta = ep.metadata;
  if (meta) {
    // Hosts
    for (const host of meta.hosts) {
      console.log(`Host: ${host.host_name} (${host.host_company})`);
    }

    // Guests with social links and occupation
    for (const guest of meta.guests) {
      console.log(`Guest: ${guest.guest_name}`);
      console.log(`  Occupation: ${guest.guest_occupation}`);
      console.log(`  Social: ${guest.guest_social_media_links?.join(', ')}`);
    }

    // Speaker label to name mapping
    console.log('Speakers:', meta.speakers);
    // { "SPEAKER_01": "John Smith", "SPEAKER_02": "Jane Doe" }

    // AI-generated summaries
    console.log('Summary:', meta.summary_short);
    console.log('Keywords:', meta.summary_keywords);
  }
}

Transcript formatting options:

// Clean text without timestamps
const clean = await client.episodes.search({
  query: 'AI',
  remove_timestamps: true,
  remove_speaker_labels: true,
});

// Paragraphs (merges segments)
const paragraphs = await client.episodes.search({
  query: 'AI',
  transcript_formatter: 'paragraphs',
});

// Exclude transcript entirely (saves bandwidth)
const noTranscript = await client.episodes.search({
  query: 'AI',
  exclude_transcript: true,
});

Error Handling

All API errors throw a PodscanError with structured details:

import { PodscanClient, PodscanError } from 'podscan';

try {
  const results = await client.episodes.search({ query: 'test' });
} catch (error) {
  if (error instanceof PodscanError) {
    console.error(error.code);    // 'quota_exceeded', 'not_found', etc.
    console.error(error.message);  // Human-readable message
    console.error(error.status);   // HTTP status code (0 for network/timeout errors)
    console.error(error.details);  // Additional context from the API
  }
}

Error Codes

| Code | Description | |---|---| | api_error | Generic API error | | not_found | Resource does not exist | | quota_exceeded | Daily request limit reached | | access_denied | Resource belongs to another team | | validation_error | Invalid parameter value | | timeout | Request timed out | | network_error | Network connectivity issue |

Rate Limits

Rate-limit info is available after each request:

await client.episodes.search({ query: 'test' });

console.log(client.rateLimit);
// { limit: 2000, remaining: 1999, used: 1, resetsAt: '2026-02-17T00:00:00Z' }

Requirements

  • Node.js >= 18.0.0 (uses native fetch)
  • A Podscan account with API access

License

MIT