podscan 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +484 -0
- package/dist/index.cjs +630 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +849 -0
- package/dist/index.d.ts +849 -0
- package/dist/index.js +593 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Podscan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# podscan
|
|
2
|
+
|
|
3
|
+
Lightweight, zero-dependency TypeScript SDK for the [Podscan REST API](https://podscan.fm/rest-api). Optimized for AWS Lambda and serverless environments.
|
|
4
|
+
|
|
5
|
+
- **Zero runtime dependencies** -- uses native `fetch` (Node 18+)
|
|
6
|
+
- **Dual format** -- ESM and CommonJS
|
|
7
|
+
- **Fully typed** -- complete TypeScript definitions including episode metadata (hosts, guests, speakers)
|
|
8
|
+
- **Auto-pagination** -- `searchAll()` async iterators walk all pages automatically
|
|
9
|
+
- **Time period helpers** -- `periods.thisWeek()`, `periods.lastMonth()`, etc.
|
|
10
|
+
- **Delta sync** -- checkpoint-based tracking for incremental data pulls
|
|
11
|
+
- **Tiny** -- under 10 KB minified (ESM)
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install podscan
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { PodscanClient } from 'podscan';
|
|
23
|
+
|
|
24
|
+
const client = new PodscanClient({
|
|
25
|
+
apiKey: process.env.PODSCAN_API_KEY!,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Search episodes
|
|
29
|
+
const results = await client.episodes.search({
|
|
30
|
+
query: 'AI marketing',
|
|
31
|
+
language: 'en',
|
|
32
|
+
per_page: 10,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log(`Found ${results.pagination.total} episodes`);
|
|
36
|
+
|
|
37
|
+
for (const episode of results.episodes) {
|
|
38
|
+
console.log(`${episode.episode_title} — ${episode.podcast?.podcast_name}`);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Lambda Usage
|
|
43
|
+
|
|
44
|
+
The SDK uses native `fetch` with no external dependencies, so cold starts are fast and bundle size is minimal.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { PodscanClient } from 'podscan';
|
|
48
|
+
|
|
49
|
+
const client = new PodscanClient({
|
|
50
|
+
apiKey: process.env.PODSCAN_API_KEY!,
|
|
51
|
+
timeout: 10_000, // tighter timeout for Lambda
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const handler = async (event: any) => {
|
|
55
|
+
const results = await client.episodes.search({
|
|
56
|
+
query: event.queryStringParameters?.q ?? 'tech',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
statusCode: 200,
|
|
61
|
+
body: JSON.stringify(results.episodes),
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const client = new PodscanClient({
|
|
70
|
+
apiKey: 'your-api-key', // Required. Bearer token for Podscan API.
|
|
71
|
+
baseUrl: 'https://...', // Optional. Override API base URL.
|
|
72
|
+
timeout: 30_000, // Optional. Request timeout in ms (default: 30000).
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
All methods return typed promises. Parameters mirror the [Podscan API docs](https://podscan.fm/docs/api).
|
|
79
|
+
|
|
80
|
+
### `client.episodes`
|
|
81
|
+
|
|
82
|
+
| Method | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `search(params)` | Full-text search across episode transcripts, titles, and descriptions |
|
|
85
|
+
| `get(params)` | Get detailed info about a specific episode |
|
|
86
|
+
| `getRecent(params?)` | Get the most recently published episodes |
|
|
87
|
+
| `getByPodcast(params)` | List all episodes for a specific podcast |
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Search episodes with filters
|
|
91
|
+
const results = await client.episodes.search({
|
|
92
|
+
query: 'machine learning',
|
|
93
|
+
language: 'en',
|
|
94
|
+
has_guests: true,
|
|
95
|
+
since: '2026-01-01',
|
|
96
|
+
order_by: 'relevance',
|
|
97
|
+
per_page: 25,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Get episode details with transcript
|
|
101
|
+
const episode = await client.episodes.get({
|
|
102
|
+
episode_id: 'ep_m9v2x7kq4pn8rjsw',
|
|
103
|
+
include_transcript: true,
|
|
104
|
+
include_entities: true,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Get recent episodes
|
|
108
|
+
const recent = await client.episodes.getRecent({ limit: 10, language: 'en' });
|
|
109
|
+
|
|
110
|
+
// List episodes for a podcast
|
|
111
|
+
const podcastEpisodes = await client.episodes.getByPodcast({
|
|
112
|
+
podcast_id: 'pd_ka86x53ynan9wgdv',
|
|
113
|
+
order_by: 'posted_at',
|
|
114
|
+
per_page: 50,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `client.podcasts`
|
|
119
|
+
|
|
120
|
+
| Method | Description |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `search(params)` | Search podcasts by name, topic, or characteristics |
|
|
123
|
+
| `get(params)` | Get detailed info about a specific podcast |
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// Search podcasts
|
|
127
|
+
const podcasts = await client.podcasts.search({
|
|
128
|
+
query: 'business',
|
|
129
|
+
has_guests: true,
|
|
130
|
+
min_episode_count: 50,
|
|
131
|
+
order_by: 'audience_size',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Get podcast details
|
|
135
|
+
const podcast = await client.podcasts.get({
|
|
136
|
+
podcast_id: 'pd_ka86x53ynan9wgdv',
|
|
137
|
+
include_episodes: true,
|
|
138
|
+
episode_limit: 5,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `client.alerts`
|
|
143
|
+
|
|
144
|
+
| Method | Description |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `list(params?)` | List your team's content monitoring alerts |
|
|
147
|
+
| `getMentions(params)` | Get mentions found by a specific alert |
|
|
148
|
+
| `create(params)` | Create a new content monitoring alert |
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// List alerts
|
|
152
|
+
const alerts = await client.alerts.list({ enabled_only: true });
|
|
153
|
+
|
|
154
|
+
// Get mentions for an alert
|
|
155
|
+
const mentions = await client.alerts.getMentions({
|
|
156
|
+
alert_id: 'al_h3f5g8k2m7n4p9q6',
|
|
157
|
+
since: '2026-02-01',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Create an alert with filter expressions
|
|
161
|
+
const alert = await client.alerts.create({
|
|
162
|
+
name: 'Brand Monitor',
|
|
163
|
+
filters: '"Acme Corp"\nAcme AND (product OR service)',
|
|
164
|
+
webhook_url: 'https://example.com/webhook',
|
|
165
|
+
webhook_active: true,
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `client.topics`
|
|
170
|
+
|
|
171
|
+
| Method | Description |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `search(params)` | Discover topics discussed across podcasts |
|
|
174
|
+
| `get(params)` | Get detailed info about a specific topic |
|
|
175
|
+
| `getEpisodes(params)` | Get episodes where a topic was mentioned |
|
|
176
|
+
| `getTrending(params?)` | Get currently trending topics |
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Search topics
|
|
180
|
+
const topics = await client.topics.search({
|
|
181
|
+
query: 'cryptocurrency',
|
|
182
|
+
min_episodes: 100,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Get topic with history
|
|
186
|
+
const topic = await client.topics.get({
|
|
187
|
+
topic_id: 'tp_z8x6c4v2b0n9m7k5',
|
|
188
|
+
with_history: true,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Get episodes for a topic
|
|
192
|
+
const topicEpisodes = await client.topics.getEpisodes({
|
|
193
|
+
topic_id: 'tp_z8x6c4v2b0n9m7k5',
|
|
194
|
+
podcast_audience_min: 10000,
|
|
195
|
+
per_page: 25,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Get trending topics
|
|
199
|
+
const trending = await client.topics.getTrending({ period: '7d', limit: 20 });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `client.entities`
|
|
203
|
+
|
|
204
|
+
| Method | Description |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `search(params)` | Search for people and organizations mentioned in podcasts |
|
|
207
|
+
| `get(params)` | Get detailed info about a person or organization |
|
|
208
|
+
| `getAppearances(params)` | Get all podcast appearances for an entity |
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// Search entities
|
|
212
|
+
const entities = await client.entities.search({
|
|
213
|
+
query: 'Elon',
|
|
214
|
+
entity_type: 'person',
|
|
215
|
+
min_appearances: 100,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Get entity with recent appearances
|
|
219
|
+
const entity = await client.entities.get({
|
|
220
|
+
entity_id: 'en_p4o2i8u6y3t1r5e9',
|
|
221
|
+
with_appearances: true,
|
|
222
|
+
appearances_limit: 10,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Get all appearances filtered by role
|
|
226
|
+
const appearances = await client.entities.getAppearances({
|
|
227
|
+
entity_id: 'en_p4o2i8u6y3t1r5e9',
|
|
228
|
+
role: 'guest',
|
|
229
|
+
order_dir: 'desc',
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `client.lists`
|
|
234
|
+
|
|
235
|
+
| Method | Description |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `list(params?)` | Get all lists/collections for your team |
|
|
238
|
+
| `getItems(params)` | Get contents of a specific list |
|
|
239
|
+
| `addItems(params)` | Add items to a list (podcasts, episodes, entities, topics) |
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// List all collections
|
|
243
|
+
const lists = await client.lists.list();
|
|
244
|
+
|
|
245
|
+
// Get list items filtered by type
|
|
246
|
+
const items = await client.lists.getItems({
|
|
247
|
+
list_id: 'cl_q9w3e5r7t1y4u8i2',
|
|
248
|
+
item_type: 'podcasts',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Add items to a list
|
|
252
|
+
const result = await client.lists.addItems({
|
|
253
|
+
list_id: 'cl_q9w3e5r7t1y4u8i2',
|
|
254
|
+
item_ids: 'pd_ka86x53ynan9wgdv,ep_m9v2x7kq4pn8rjsw',
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### `client.publishers`
|
|
259
|
+
|
|
260
|
+
| Method | Description |
|
|
261
|
+
|---|---|
|
|
262
|
+
| `get(params)` | Get publisher info with their podcast portfolio |
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const publisher = await client.publishers.get({
|
|
266
|
+
publisher_id: 'pb_l7k5j3h1g9f6d4s2',
|
|
267
|
+
include_podcasts: true,
|
|
268
|
+
podcast_limit: 20,
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Time Periods
|
|
273
|
+
|
|
274
|
+
The `periods` helper computes date ranges you can spread into any search call:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { PodscanClient, periods } from 'podscan';
|
|
278
|
+
|
|
279
|
+
const client = new PodscanClient({ apiKey: process.env.PODSCAN_API_KEY! });
|
|
280
|
+
|
|
281
|
+
// This week's AI episodes
|
|
282
|
+
const results = await client.episodes.search({
|
|
283
|
+
query: 'AI',
|
|
284
|
+
...periods.thisWeek(),
|
|
285
|
+
per_page: 50,
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Available presets:
|
|
290
|
+
|
|
291
|
+
| Method | Range |
|
|
292
|
+
|---|---|
|
|
293
|
+
| `periods.today()` | Midnight UTC today to now |
|
|
294
|
+
| `periods.yesterday()` | Yesterday midnight to today midnight |
|
|
295
|
+
| `periods.last24Hours()` | Rolling 24-hour window |
|
|
296
|
+
| `periods.thisWeek()` | Monday 00:00 UTC to now |
|
|
297
|
+
| `periods.lastWeek()` | Previous Monday to this Monday |
|
|
298
|
+
| `periods.thisMonth()` | 1st of month to now |
|
|
299
|
+
| `periods.lastMonth()` | 1st of last month to 1st of this month |
|
|
300
|
+
| `periods.lastNDays(n)` | Rolling N-day window |
|
|
301
|
+
| `periods.lastNHours(n)` | Rolling N-hour window |
|
|
302
|
+
| `periods.since(date)` | Everything after a Date or ISO string |
|
|
303
|
+
|
|
304
|
+
All dates are UTC ISO 8601 strings. Each returns `{ since, before? }`.
|
|
305
|
+
|
|
306
|
+
## Auto-Pagination
|
|
307
|
+
|
|
308
|
+
Every paginated resource has a `searchAll()` method that returns an async iterator, walking all pages automatically:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// Iterate every matching episode across all pages
|
|
312
|
+
for await (const episode of client.episodes.searchAll({
|
|
313
|
+
query: 'artificial intelligence',
|
|
314
|
+
...periods.thisWeek(),
|
|
315
|
+
has_guests: true,
|
|
316
|
+
})) {
|
|
317
|
+
console.log(episode.episode_title);
|
|
318
|
+
console.log(' Guests:', episode.metadata?.guests.map(g => g.guest_name).join(', '));
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Available auto-paginating methods:
|
|
323
|
+
|
|
324
|
+
| Resource | Method | Yields |
|
|
325
|
+
|---|---|---|
|
|
326
|
+
| `client.episodes` | `searchAll(params)` | `Episode` |
|
|
327
|
+
| `client.episodes` | `getByPodcastAll(params)` | `Episode` |
|
|
328
|
+
| `client.podcasts` | `searchAll(params)` | `Podcast` |
|
|
329
|
+
| `client.topics` | `searchAll(params)` | `TopicSummary` |
|
|
330
|
+
| `client.topics` | `getEpisodesAll(params)` | `Episode` |
|
|
331
|
+
| `client.entities` | `searchAll(params)` | `Entity` |
|
|
332
|
+
|
|
333
|
+
## Delta Sync (Checkpoints)
|
|
334
|
+
|
|
335
|
+
Track what you've already pulled so the next run only fetches new content:
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { PodscanClient, periods } from 'podscan';
|
|
339
|
+
|
|
340
|
+
const client = new PodscanClient({ apiKey: process.env.PODSCAN_API_KEY! });
|
|
341
|
+
|
|
342
|
+
// First run: pull this week's episodes
|
|
343
|
+
const paginator = client.episodes.searchAll({
|
|
344
|
+
query: 'AI',
|
|
345
|
+
...periods.thisWeek(),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
for await (const episode of paginator) {
|
|
349
|
+
await saveToDatabase(episode);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Save checkpoint
|
|
353
|
+
const checkpoint = paginator.checkpoint();
|
|
354
|
+
// { lastSeenAt: '2026-02-16T14:30:00Z', lastSeenId: 'ep_abc123', totalSeen: 142 }
|
|
355
|
+
await saveCheckpoint(checkpoint);
|
|
356
|
+
|
|
357
|
+
// Next run: only get new episodes since last checkpoint
|
|
358
|
+
const lastCheckpoint = await loadCheckpoint();
|
|
359
|
+
const newEpisodes = client.episodes.searchAll({
|
|
360
|
+
query: 'AI',
|
|
361
|
+
since: lastCheckpoint.lastSeenAt,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
for await (const episode of newEpisodes) {
|
|
365
|
+
await saveToDatabase(episode);
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Transcripts and Guest Data
|
|
370
|
+
|
|
371
|
+
Every episode includes full transcript and structured metadata with host/guest/speaker info:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
const results = await client.episodes.search({
|
|
375
|
+
query: 'machine learning',
|
|
376
|
+
has_guests: true,
|
|
377
|
+
...periods.thisWeek(),
|
|
378
|
+
per_page: 10,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
for (const ep of results.episodes) {
|
|
382
|
+
// Full transcript with timestamps and speaker labels
|
|
383
|
+
console.log(ep.episode_transcript);
|
|
384
|
+
// "[00:00:08] [SPEAKER_01] Welcome to the show..."
|
|
385
|
+
|
|
386
|
+
// Structured metadata
|
|
387
|
+
const meta = ep.metadata;
|
|
388
|
+
if (meta) {
|
|
389
|
+
// Hosts
|
|
390
|
+
for (const host of meta.hosts) {
|
|
391
|
+
console.log(`Host: ${host.host_name} (${host.host_company})`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Guests with social links and occupation
|
|
395
|
+
for (const guest of meta.guests) {
|
|
396
|
+
console.log(`Guest: ${guest.guest_name}`);
|
|
397
|
+
console.log(` Occupation: ${guest.guest_occupation}`);
|
|
398
|
+
console.log(` Social: ${guest.guest_social_media_links?.join(', ')}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Speaker label to name mapping
|
|
402
|
+
console.log('Speakers:', meta.speakers);
|
|
403
|
+
// { "SPEAKER_01": "John Smith", "SPEAKER_02": "Jane Doe" }
|
|
404
|
+
|
|
405
|
+
// AI-generated summaries
|
|
406
|
+
console.log('Summary:', meta.summary_short);
|
|
407
|
+
console.log('Keywords:', meta.summary_keywords);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Transcript formatting options:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// Clean text without timestamps
|
|
416
|
+
const clean = await client.episodes.search({
|
|
417
|
+
query: 'AI',
|
|
418
|
+
remove_timestamps: true,
|
|
419
|
+
remove_speaker_labels: true,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Paragraphs (merges segments)
|
|
423
|
+
const paragraphs = await client.episodes.search({
|
|
424
|
+
query: 'AI',
|
|
425
|
+
transcript_formatter: 'paragraphs',
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Exclude transcript entirely (saves bandwidth)
|
|
429
|
+
const noTranscript = await client.episodes.search({
|
|
430
|
+
query: 'AI',
|
|
431
|
+
exclude_transcript: true,
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Error Handling
|
|
436
|
+
|
|
437
|
+
All API errors throw a `PodscanError` with structured details:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { PodscanClient, PodscanError } from 'podscan';
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
const results = await client.episodes.search({ query: 'test' });
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (error instanceof PodscanError) {
|
|
446
|
+
console.error(error.code); // 'quota_exceeded', 'not_found', etc.
|
|
447
|
+
console.error(error.message); // Human-readable message
|
|
448
|
+
console.error(error.status); // HTTP status code (0 for network/timeout errors)
|
|
449
|
+
console.error(error.details); // Additional context from the API
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Error Codes
|
|
455
|
+
|
|
456
|
+
| Code | Description |
|
|
457
|
+
|---|---|
|
|
458
|
+
| `api_error` | Generic API error |
|
|
459
|
+
| `not_found` | Resource does not exist |
|
|
460
|
+
| `quota_exceeded` | Daily request limit reached |
|
|
461
|
+
| `access_denied` | Resource belongs to another team |
|
|
462
|
+
| `validation_error` | Invalid parameter value |
|
|
463
|
+
| `timeout` | Request timed out |
|
|
464
|
+
| `network_error` | Network connectivity issue |
|
|
465
|
+
|
|
466
|
+
## Rate Limits
|
|
467
|
+
|
|
468
|
+
Rate-limit info is available after each request:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
await client.episodes.search({ query: 'test' });
|
|
472
|
+
|
|
473
|
+
console.log(client.rateLimit);
|
|
474
|
+
// { limit: 2000, remaining: 1999, used: 1, resetsAt: '2026-02-17T00:00:00Z' }
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Requirements
|
|
478
|
+
|
|
479
|
+
- Node.js >= 18.0.0 (uses native `fetch`)
|
|
480
|
+
- A [Podscan](https://podscan.fm) account with API access
|
|
481
|
+
|
|
482
|
+
## License
|
|
483
|
+
|
|
484
|
+
MIT
|