@transcribe-api/sdk 0.1.6 → 0.1.8

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.
Files changed (4) hide show
  1. package/README.md +52 -1
  2. package/index.js +164 -10
  3. package/package.json +1 -1
  4. package/worker.js +164 -10
package/README.md CHANGED
@@ -68,6 +68,10 @@ const client = new TranscribeAPI({
68
68
  apiKey: "YOUR_API_KEY",
69
69
  baseUrl: "https://api.transcribeapi.com/v1",
70
70
  uploadConcurrency: 4,
71
+ autoReload: {
72
+ amount: 10,
73
+ ifBalanceBelow: 20,
74
+ },
71
75
  showLogs: true,
72
76
  logger: console,
73
77
  polling: {
@@ -82,6 +86,7 @@ const client = new TranscribeAPI({
82
86
  | `apiKey` | Required. Your Transcribe API key. |
83
87
  | `baseUrl` | Optional. Defaults to `https://api.transcribeapi.com/v1`. |
84
88
  | `uploadConcurrency` | Optional. Number of in-flight upload PUTs. Defaults to `1` and is capped at `32`. |
89
+ | `autoReload` | Optional. Automatically calls `POST /v1/add-funds` after a transcription result when `remaining_balance` is below `ifBalanceBelow`. |
85
90
  | `showLogs` | Optional. Prints upload progress, upload completion, polling status, and final result info. |
86
91
  | `logger` | Optional. Logger object with a `log` method. Defaults to `console`. |
87
92
  | `polling` | Optional. `{ interval, timeout }` in seconds. `interval` must be at least `10`. Omit or set to `null`/`false` to disable automatic polling. |
@@ -185,7 +190,53 @@ You can also pass a comma-separated string:
185
190
  exclude: "metadata,billing"
186
191
  ```
187
192
 
188
- If `exclude` leaves only one transcript field and removes metadata, billing, and detected language, the API returns that remaining value directly instead of a JSON object. For example, text-only responses return a plain string, VTT-only responses return a plain string, and segments-only responses return an array.
193
+ For single-file outputs, if `exclude` leaves only one transcript field and removes metadata, billing, and detected language, the API returns that remaining value directly instead of a JSON object. Text-only responses return a plain string, VTT-only responses return a plain string, and segments-only responses return an array. Multi-file outputs still return JSON so each result stays associated with its `reference_id`.
194
+
195
+ If `autoReload` is enabled, the SDK always keeps billing in the request even if you included `billing` in `exclude`. It needs `remaining_balance` to decide whether to call `POST /v1/add-funds`.
196
+
197
+ ## Auto reload
198
+
199
+ ```js
200
+ const client = new TranscribeAPI({
201
+ apiKey: process.env.TRANSCRIBE_API_KEY,
202
+ autoReload: {
203
+ amount: 10,
204
+ ifBalanceBelow: 20,
205
+ },
206
+ });
207
+ ```
208
+
209
+ When `autoReload` is enabled, the SDK:
210
+
211
+ - checks `remaining_balance` after a transcription result
212
+ - only calls `POST /v1/add-funds` when the balance is below `ifBalanceBelow`
213
+ - sends `{ amount, if_balance_below }` to the API for backend-side protection too
214
+ - ignores `billing` inside `exclude` so the SDK can always read `remaining_balance`
215
+
216
+ Auto reload only runs when the transcription result returned to the SDK includes `remaining_balance`. If you disable polling for async jobs, immediate non-terminal async responses do not have enough billing data for the SDK to auto reload yet.
217
+
218
+ ### Manual addFunds for webhooks
219
+
220
+ When using webhooks with batch jobs, the transcription result goes to your webhook URL instead of the SDK. The `autoReload` config won't fire because the SDK never sees the result. Call `client.addFunds()` inside your webhook handler instead:
221
+
222
+ ```js
223
+ // Webhook handler (Express example)
224
+ app.post("/webhook", async (req, res) => {
225
+ const { remaining_balance } = req.body;
226
+
227
+ if (remaining_balance < 20) {
228
+ const client = new TranscribeAPI({ apiKey: process.env.TRANSCRIBE_API_KEY });
229
+ await client.addFunds(10, { ifBalanceBelow: 20 });
230
+ }
231
+
232
+ res.sendStatus(200);
233
+ });
234
+ ```
235
+
236
+ `addFunds(amount, { ifBalanceBelow })` calls `POST /v1/add-funds`:
237
+
238
+ - `amount` — whole dollar amount from 5 to 100.
239
+ - `ifBalanceBelow` — server-side guard. The API only charges if your current balance is actually below this threshold, making it safe against duplicate webhook deliveries.
189
240
 
190
241
  ## Webhooks
191
242
 
package/index.js CHANGED
@@ -143,6 +143,54 @@ function normalizePollingConfig(polling) {
143
143
  };
144
144
  }
145
145
 
146
+ function normalizeAutoReloadConfig(autoReload) {
147
+ if (autoReload === undefined || autoReload === null || autoReload === false) {
148
+ return null;
149
+ }
150
+ if (typeof autoReload !== "object" || Array.isArray(autoReload)) {
151
+ throw new TranscribeAPIError("`autoReload` must be an object with `amount` and `ifBalanceBelow`.", {
152
+ code: "invalid_auto_reload_config",
153
+ });
154
+ }
155
+
156
+ const amount = Number.parseInt(String(autoReload.amount ?? ""), 10);
157
+ if (!Number.isInteger(amount) || amount < 5 || amount > 100) {
158
+ throw new TranscribeAPIError("`autoReload.amount` must be a whole dollar value from 5 to 100.", {
159
+ code: "invalid_auto_reload_amount",
160
+ });
161
+ }
162
+
163
+ const rawIfBalanceBelow = autoReload.ifBalanceBelow ?? autoReload.if_balance_below;
164
+ const ifBalanceBelow = Number(rawIfBalanceBelow);
165
+ if (!Number.isFinite(ifBalanceBelow) || ifBalanceBelow < 0) {
166
+ throw new TranscribeAPIError("`autoReload.ifBalanceBelow` must be a number >= 0.", {
167
+ code: "invalid_auto_reload_if_balance_below",
168
+ });
169
+ }
170
+
171
+ return {
172
+ amount,
173
+ ifBalanceBelow,
174
+ };
175
+ }
176
+
177
+ function normalizeExcludeList(exclude) {
178
+ if (exclude === undefined || exclude === null || exclude === "") {
179
+ return [];
180
+ }
181
+ if (Array.isArray(exclude)) {
182
+ return exclude.map((item) => String(item || "").trim()).filter(Boolean);
183
+ }
184
+ return String(exclude)
185
+ .split(",")
186
+ .map((item) => item.trim())
187
+ .filter(Boolean);
188
+ }
189
+
190
+ function stringifyExclude(excludeList) {
191
+ return excludeList.length ? excludeList.join(",") : null;
192
+ }
193
+
146
194
  function contentTypeFromName(name = "") {
147
195
  const lower = String(name).toLowerCase();
148
196
  if (lower.endsWith(".mp3")) return "audio/mpeg";
@@ -1229,6 +1277,7 @@ export class TranscribeAPI {
1229
1277
  showLogs = false,
1230
1278
  logger = console,
1231
1279
  polling = null,
1280
+ autoReload = null,
1232
1281
  } = {}) {
1233
1282
  if (!apiKey) {
1234
1283
  throw new Error("Missing API key");
@@ -1240,6 +1289,7 @@ export class TranscribeAPI {
1240
1289
  this.showLogs = Boolean(showLogs);
1241
1290
  this.logger = logger || console;
1242
1291
  this.polling = normalizePollingConfig(polling);
1292
+ this.autoReload = normalizeAutoReloadConfig(autoReload);
1243
1293
  this.jobs = {
1244
1294
  createBigFile: (options) => this.createBigFileJob(options),
1245
1295
  createBatch: (options) => this.createBatchJob(options),
@@ -1265,6 +1315,54 @@ export class TranscribeAPI {
1265
1315
  return retryable ? retry(run) : run();
1266
1316
  }
1267
1317
 
1318
+ getEffectiveExclude(exclude) {
1319
+ const excludeList = normalizeExcludeList(exclude);
1320
+ if (!this.autoReload) {
1321
+ return excludeList;
1322
+ }
1323
+ return excludeList.filter((field) => field !== "billing");
1324
+ }
1325
+
1326
+ async addFunds(amount, { ifBalanceBelow } = {}) {
1327
+ const body = { amount };
1328
+ if (ifBalanceBelow !== undefined && ifBalanceBelow !== null) {
1329
+ body.if_balance_below = ifBalanceBelow;
1330
+ }
1331
+ return this.requestJson("/add-funds", {
1332
+ method: "POST",
1333
+ body,
1334
+ retryable: true,
1335
+ });
1336
+ }
1337
+
1338
+ async maybeAutoReload(result, { showLogs = this.showLogs, logger = this.logger } = {}) {
1339
+ if (!this.autoReload || !result || typeof result !== "object" || Array.isArray(result)) {
1340
+ return result;
1341
+ }
1342
+
1343
+ const remainingBalance = Number(result.remaining_balance);
1344
+ if (!Number.isFinite(remainingBalance) || remainingBalance >= this.autoReload.ifBalanceBelow) {
1345
+ return result;
1346
+ }
1347
+
1348
+ try {
1349
+ const autoReloadResult = await this.addFunds(this.autoReload.amount, {
1350
+ ifBalanceBelow: this.autoReload.ifBalanceBelow,
1351
+ });
1352
+ if (showLogs && typeof logger?.log === "function") {
1353
+ logger.log(JSON.stringify(autoReloadResult, null, 2));
1354
+ }
1355
+ } catch (error) {
1356
+ if (showLogs && typeof logger?.log === "function") {
1357
+ logger.log(JSON.stringify({
1358
+ auto_reload_error: extractErrorInfo(error),
1359
+ }, null, 2));
1360
+ }
1361
+ }
1362
+
1363
+ return result;
1364
+ }
1365
+
1268
1366
  async waitForJobCompletion(jobId, {
1269
1367
  polling = this.polling,
1270
1368
  showLogs = this.showLogs,
@@ -1325,6 +1423,7 @@ export class TranscribeAPI {
1325
1423
  exclude,
1326
1424
  uploadConcurrency,
1327
1425
  } = {}) {
1426
+ const effectiveExclude = this.getEffectiveExclude(exclude);
1328
1427
  const progress = composeProgressHandler({
1329
1428
  onProgress,
1330
1429
  showLogs: showLogs ?? this.showLogs,
@@ -1353,7 +1452,7 @@ export class TranscribeAPI {
1353
1452
  logger: logger ?? this.logger,
1354
1453
  uploadConcurrency,
1355
1454
  language,
1356
- exclude,
1455
+ exclude: effectiveExclude,
1357
1456
  });
1358
1457
  }
1359
1458
 
@@ -1364,8 +1463,6 @@ export class TranscribeAPI {
1364
1463
  : (normalizedItem.durationEstimateSec || await estimateDurationFromFile(normalizedSingleFile));
1365
1464
  const effectiveLanguage = normalizedItem.hasLanguage ? normalizedItem.language : normalizeLanguageCode(language);
1366
1465
  const isAsync = files.length > 1
1367
- || Boolean(normalizedItem.url)
1368
- || Boolean(webhookUrl)
1369
1466
  || (
1370
1467
  normalizedSingleFile
1371
1468
  && (
@@ -1383,7 +1480,19 @@ export class TranscribeAPI {
1383
1480
  logger: logger ?? this.logger,
1384
1481
  uploadConcurrency,
1385
1482
  language,
1386
- exclude,
1483
+ exclude: effectiveExclude,
1484
+ });
1485
+ }
1486
+
1487
+ if (normalizedItem.url) {
1488
+ return this.transcribeUrlDirect({
1489
+ url: normalizedItem.url,
1490
+ referenceId: normalizedItem.referenceId,
1491
+ language: effectiveLanguage,
1492
+ exclude: effectiveExclude,
1493
+ webhookUrl,
1494
+ showLogs: showLogs ?? this.showLogs,
1495
+ logger: logger ?? this.logger,
1387
1496
  });
1388
1497
  }
1389
1498
 
@@ -1391,7 +1500,7 @@ export class TranscribeAPI {
1391
1500
  file: normalizedSingleFile,
1392
1501
  referenceId: normalizedItem.referenceId,
1393
1502
  language: effectiveLanguage,
1394
- exclude,
1503
+ exclude: effectiveExclude,
1395
1504
  webhookUrl,
1396
1505
  showLogs: showLogs ?? this.showLogs,
1397
1506
  logger: logger ?? this.logger,
@@ -1440,7 +1549,10 @@ export class TranscribeAPI {
1440
1549
  if ((showLogs ?? this.showLogs) && TERMINAL_JOB_STATUSES.has(String(result?.job_status || ""))) {
1441
1550
  logTerminalAsyncResult(result, logger ?? this.logger);
1442
1551
  }
1443
- return result;
1552
+ return this.maybeAutoReload(result, {
1553
+ showLogs: showLogs ?? this.showLogs,
1554
+ logger: logger ?? this.logger,
1555
+ });
1444
1556
  }
1445
1557
 
1446
1558
  async transcribeDirect({
@@ -1460,7 +1572,8 @@ export class TranscribeAPI {
1460
1572
  if (String(referenceId || "").trim()) form.set("reference_id", String(referenceId).trim());
1461
1573
  form.set("file", directFile, directFile.name);
1462
1574
  if (language) form.set("language", language);
1463
- if (exclude) form.set("exclude", Array.isArray(exclude) ? exclude.join(",") : exclude);
1575
+ const excludeValue = stringifyExclude(this.getEffectiveExclude(exclude));
1576
+ if (excludeValue) form.set("exclude", excludeValue);
1464
1577
  if (webhookUrl) form.set("webhook_url", webhookUrl);
1465
1578
 
1466
1579
  const response = await fetch(`${this.baseUrl}/transcribe`, {
@@ -1479,10 +1592,51 @@ export class TranscribeAPI {
1479
1592
  initialJob: result,
1480
1593
  });
1481
1594
  }
1595
+ const finalResult = await this.maybeAutoReload(result, { showLogs, logger });
1482
1596
  if (showLogs && typeof logger?.log === "function") {
1483
- logger.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
1597
+ logger.log(typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult, null, 2));
1484
1598
  }
1485
- return result;
1599
+ return finalResult;
1600
+ }
1601
+
1602
+ async transcribeUrlDirect({
1603
+ url,
1604
+ referenceId,
1605
+ language,
1606
+ exclude,
1607
+ webhookUrl,
1608
+ showLogs = false,
1609
+ logger = console,
1610
+ } = {}) {
1611
+ const form = new FormData();
1612
+ form.set("url", String(url || "").trim());
1613
+ if (String(referenceId || "").trim()) form.set("reference_id", String(referenceId).trim());
1614
+ if (language) form.set("language", language);
1615
+ const excludeValue = stringifyExclude(this.getEffectiveExclude(exclude));
1616
+ if (excludeValue) form.set("exclude", excludeValue);
1617
+ if (webhookUrl) form.set("webhook_url", webhookUrl);
1618
+
1619
+ const response = await fetch(`${this.baseUrl}/transcribe`, {
1620
+ method: "POST",
1621
+ headers: {
1622
+ Authorization: `Bearer ${this.apiKey}`,
1623
+ },
1624
+ body: form,
1625
+ });
1626
+ const result = await parseApiResponse(response);
1627
+ if (this.polling && result && typeof result === "object" && result.job_status && !TERMINAL_JOB_STATUSES.has(String(result.job_status))) {
1628
+ return this.waitForJobCompletion(result.job_id, {
1629
+ polling: this.polling,
1630
+ showLogs,
1631
+ logger,
1632
+ initialJob: result,
1633
+ });
1634
+ }
1635
+ const finalResult = await this.maybeAutoReload(result, { showLogs, logger });
1636
+ if (showLogs && typeof logger?.log === "function") {
1637
+ logger.log(typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult, null, 2));
1638
+ }
1639
+ return finalResult;
1486
1640
  }
1487
1641
 
1488
1642
  async transcribeMany({
@@ -1602,7 +1756,7 @@ export class TranscribeAPI {
1602
1756
  )),
1603
1757
  ...(normalizedLanguage ? { language: normalizedLanguage } : {}),
1604
1758
  ...(webhookUrl ? { webhook_url: webhookUrl } : {}),
1605
- ...(exclude ? { exclude: Array.isArray(exclude) ? exclude.join(",") : exclude } : {}),
1759
+ ...(stringifyExclude(this.getEffectiveExclude(exclude)) ? { exclude: stringifyExclude(this.getEffectiveExclude(exclude)) } : {}),
1606
1760
  },
1607
1761
  retryable: true,
1608
1762
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transcribe-api/sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Official JavaScript SDK for Transcribe API.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/worker.js CHANGED
@@ -143,6 +143,54 @@ function normalizePollingConfig(polling) {
143
143
  };
144
144
  }
145
145
 
146
+ function normalizeAutoReloadConfig(autoReload) {
147
+ if (autoReload === undefined || autoReload === null || autoReload === false) {
148
+ return null;
149
+ }
150
+ if (typeof autoReload !== "object" || Array.isArray(autoReload)) {
151
+ throw new TranscribeAPIError("`autoReload` must be an object with `amount` and `ifBalanceBelow`.", {
152
+ code: "invalid_auto_reload_config",
153
+ });
154
+ }
155
+
156
+ const amount = Number.parseInt(String(autoReload.amount ?? ""), 10);
157
+ if (!Number.isInteger(amount) || amount < 5 || amount > 100) {
158
+ throw new TranscribeAPIError("`autoReload.amount` must be a whole dollar value from 5 to 100.", {
159
+ code: "invalid_auto_reload_amount",
160
+ });
161
+ }
162
+
163
+ const rawIfBalanceBelow = autoReload.ifBalanceBelow ?? autoReload.if_balance_below;
164
+ const ifBalanceBelow = Number(rawIfBalanceBelow);
165
+ if (!Number.isFinite(ifBalanceBelow) || ifBalanceBelow < 0) {
166
+ throw new TranscribeAPIError("`autoReload.ifBalanceBelow` must be a number >= 0.", {
167
+ code: "invalid_auto_reload_if_balance_below",
168
+ });
169
+ }
170
+
171
+ return {
172
+ amount,
173
+ ifBalanceBelow,
174
+ };
175
+ }
176
+
177
+ function normalizeExcludeList(exclude) {
178
+ if (exclude === undefined || exclude === null || exclude === "") {
179
+ return [];
180
+ }
181
+ if (Array.isArray(exclude)) {
182
+ return exclude.map((item) => String(item || "").trim()).filter(Boolean);
183
+ }
184
+ return String(exclude)
185
+ .split(",")
186
+ .map((item) => item.trim())
187
+ .filter(Boolean);
188
+ }
189
+
190
+ function stringifyExclude(excludeList) {
191
+ return excludeList.length ? excludeList.join(",") : null;
192
+ }
193
+
146
194
  function contentTypeFromName(name = "") {
147
195
  const lower = String(name).toLowerCase();
148
196
  if (lower.endsWith(".mp3")) return "audio/mpeg";
@@ -1229,6 +1277,7 @@ export class TranscribeAPI {
1229
1277
  showLogs = false,
1230
1278
  logger = console,
1231
1279
  polling = null,
1280
+ autoReload = null,
1232
1281
  } = {}) {
1233
1282
  if (!apiKey) {
1234
1283
  throw new Error("Missing API key");
@@ -1240,6 +1289,7 @@ export class TranscribeAPI {
1240
1289
  this.showLogs = Boolean(showLogs);
1241
1290
  this.logger = logger || console;
1242
1291
  this.polling = normalizePollingConfig(polling);
1292
+ this.autoReload = normalizeAutoReloadConfig(autoReload);
1243
1293
  this.jobs = {
1244
1294
  createBigFile: (options) => this.createBigFileJob(options),
1245
1295
  createBatch: (options) => this.createBatchJob(options),
@@ -1265,6 +1315,54 @@ export class TranscribeAPI {
1265
1315
  return retryable ? retry(run) : run();
1266
1316
  }
1267
1317
 
1318
+ getEffectiveExclude(exclude) {
1319
+ const excludeList = normalizeExcludeList(exclude);
1320
+ if (!this.autoReload) {
1321
+ return excludeList;
1322
+ }
1323
+ return excludeList.filter((field) => field !== "billing");
1324
+ }
1325
+
1326
+ async addFunds(amount, { ifBalanceBelow } = {}) {
1327
+ const body = { amount };
1328
+ if (ifBalanceBelow !== undefined && ifBalanceBelow !== null) {
1329
+ body.if_balance_below = ifBalanceBelow;
1330
+ }
1331
+ return this.requestJson("/add-funds", {
1332
+ method: "POST",
1333
+ body,
1334
+ retryable: true,
1335
+ });
1336
+ }
1337
+
1338
+ async maybeAutoReload(result, { showLogs = this.showLogs, logger = this.logger } = {}) {
1339
+ if (!this.autoReload || !result || typeof result !== "object" || Array.isArray(result)) {
1340
+ return result;
1341
+ }
1342
+
1343
+ const remainingBalance = Number(result.remaining_balance);
1344
+ if (!Number.isFinite(remainingBalance) || remainingBalance >= this.autoReload.ifBalanceBelow) {
1345
+ return result;
1346
+ }
1347
+
1348
+ try {
1349
+ const autoReloadResult = await this.addFunds(this.autoReload.amount, {
1350
+ ifBalanceBelow: this.autoReload.ifBalanceBelow,
1351
+ });
1352
+ if (showLogs && typeof logger?.log === "function") {
1353
+ logger.log(JSON.stringify(autoReloadResult, null, 2));
1354
+ }
1355
+ } catch (error) {
1356
+ if (showLogs && typeof logger?.log === "function") {
1357
+ logger.log(JSON.stringify({
1358
+ auto_reload_error: extractErrorInfo(error),
1359
+ }, null, 2));
1360
+ }
1361
+ }
1362
+
1363
+ return result;
1364
+ }
1365
+
1268
1366
  async waitForJobCompletion(jobId, {
1269
1367
  polling = this.polling,
1270
1368
  showLogs = this.showLogs,
@@ -1325,6 +1423,7 @@ export class TranscribeAPI {
1325
1423
  exclude,
1326
1424
  uploadConcurrency,
1327
1425
  } = {}) {
1426
+ const effectiveExclude = this.getEffectiveExclude(exclude);
1328
1427
  const progress = composeProgressHandler({
1329
1428
  onProgress,
1330
1429
  showLogs: showLogs ?? this.showLogs,
@@ -1353,7 +1452,7 @@ export class TranscribeAPI {
1353
1452
  logger: logger ?? this.logger,
1354
1453
  uploadConcurrency,
1355
1454
  language,
1356
- exclude,
1455
+ exclude: effectiveExclude,
1357
1456
  });
1358
1457
  }
1359
1458
 
@@ -1364,8 +1463,6 @@ export class TranscribeAPI {
1364
1463
  : (normalizedItem.durationEstimateSec || await estimateDurationFromFile(normalizedSingleFile));
1365
1464
  const effectiveLanguage = normalizedItem.hasLanguage ? normalizedItem.language : normalizeLanguageCode(language);
1366
1465
  const isAsync = files.length > 1
1367
- || Boolean(normalizedItem.url)
1368
- || Boolean(webhookUrl)
1369
1466
  || (
1370
1467
  normalizedSingleFile
1371
1468
  && (
@@ -1383,7 +1480,19 @@ export class TranscribeAPI {
1383
1480
  logger: logger ?? this.logger,
1384
1481
  uploadConcurrency,
1385
1482
  language,
1386
- exclude,
1483
+ exclude: effectiveExclude,
1484
+ });
1485
+ }
1486
+
1487
+ if (normalizedItem.url) {
1488
+ return this.transcribeUrlDirect({
1489
+ url: normalizedItem.url,
1490
+ referenceId: normalizedItem.referenceId,
1491
+ language: effectiveLanguage,
1492
+ exclude: effectiveExclude,
1493
+ webhookUrl,
1494
+ showLogs: showLogs ?? this.showLogs,
1495
+ logger: logger ?? this.logger,
1387
1496
  });
1388
1497
  }
1389
1498
 
@@ -1391,7 +1500,7 @@ export class TranscribeAPI {
1391
1500
  file: normalizedSingleFile,
1392
1501
  referenceId: normalizedItem.referenceId,
1393
1502
  language: effectiveLanguage,
1394
- exclude,
1503
+ exclude: effectiveExclude,
1395
1504
  webhookUrl,
1396
1505
  showLogs: showLogs ?? this.showLogs,
1397
1506
  logger: logger ?? this.logger,
@@ -1440,7 +1549,10 @@ export class TranscribeAPI {
1440
1549
  if ((showLogs ?? this.showLogs) && TERMINAL_JOB_STATUSES.has(String(result?.job_status || ""))) {
1441
1550
  logTerminalAsyncResult(result, logger ?? this.logger);
1442
1551
  }
1443
- return result;
1552
+ return this.maybeAutoReload(result, {
1553
+ showLogs: showLogs ?? this.showLogs,
1554
+ logger: logger ?? this.logger,
1555
+ });
1444
1556
  }
1445
1557
 
1446
1558
  async transcribeDirect({
@@ -1460,7 +1572,8 @@ export class TranscribeAPI {
1460
1572
  if (String(referenceId || "").trim()) form.set("reference_id", String(referenceId).trim());
1461
1573
  form.set("file", directFile, directFile.name);
1462
1574
  if (language) form.set("language", language);
1463
- if (exclude) form.set("exclude", Array.isArray(exclude) ? exclude.join(",") : exclude);
1575
+ const excludeValue = stringifyExclude(this.getEffectiveExclude(exclude));
1576
+ if (excludeValue) form.set("exclude", excludeValue);
1464
1577
  if (webhookUrl) form.set("webhook_url", webhookUrl);
1465
1578
 
1466
1579
  const response = await fetch(`${this.baseUrl}/transcribe`, {
@@ -1479,10 +1592,51 @@ export class TranscribeAPI {
1479
1592
  initialJob: result,
1480
1593
  });
1481
1594
  }
1595
+ const finalResult = await this.maybeAutoReload(result, { showLogs, logger });
1482
1596
  if (showLogs && typeof logger?.log === "function") {
1483
- logger.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
1597
+ logger.log(typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult, null, 2));
1484
1598
  }
1485
- return result;
1599
+ return finalResult;
1600
+ }
1601
+
1602
+ async transcribeUrlDirect({
1603
+ url,
1604
+ referenceId,
1605
+ language,
1606
+ exclude,
1607
+ webhookUrl,
1608
+ showLogs = false,
1609
+ logger = console,
1610
+ } = {}) {
1611
+ const form = new FormData();
1612
+ form.set("url", String(url || "").trim());
1613
+ if (String(referenceId || "").trim()) form.set("reference_id", String(referenceId).trim());
1614
+ if (language) form.set("language", language);
1615
+ const excludeValue = stringifyExclude(this.getEffectiveExclude(exclude));
1616
+ if (excludeValue) form.set("exclude", excludeValue);
1617
+ if (webhookUrl) form.set("webhook_url", webhookUrl);
1618
+
1619
+ const response = await fetch(`${this.baseUrl}/transcribe`, {
1620
+ method: "POST",
1621
+ headers: {
1622
+ Authorization: `Bearer ${this.apiKey}`,
1623
+ },
1624
+ body: form,
1625
+ });
1626
+ const result = await parseApiResponse(response);
1627
+ if (this.polling && result && typeof result === "object" && result.job_status && !TERMINAL_JOB_STATUSES.has(String(result.job_status))) {
1628
+ return this.waitForJobCompletion(result.job_id, {
1629
+ polling: this.polling,
1630
+ showLogs,
1631
+ logger,
1632
+ initialJob: result,
1633
+ });
1634
+ }
1635
+ const finalResult = await this.maybeAutoReload(result, { showLogs, logger });
1636
+ if (showLogs && typeof logger?.log === "function") {
1637
+ logger.log(typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult, null, 2));
1638
+ }
1639
+ return finalResult;
1486
1640
  }
1487
1641
 
1488
1642
  async transcribeMany({
@@ -1602,7 +1756,7 @@ export class TranscribeAPI {
1602
1756
  )),
1603
1757
  ...(normalizedLanguage ? { language: normalizedLanguage } : {}),
1604
1758
  ...(webhookUrl ? { webhook_url: webhookUrl } : {}),
1605
- ...(exclude ? { exclude: Array.isArray(exclude) ? exclude.join(",") : exclude } : {}),
1759
+ ...(stringifyExclude(this.getEffectiveExclude(exclude)) ? { exclude: stringifyExclude(this.getEffectiveExclude(exclude)) } : {}),
1606
1760
  },
1607
1761
  retryable: true,
1608
1762
  });