@vocalstack/js-sdk 1.1.0 → 2.0.0-alpha.1

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 (73) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +112 -65
  3. package/SKILL.md +249 -0
  4. package/dist/errors.d.ts +22 -0
  5. package/dist/errors.js +56 -0
  6. package/dist/file.d.ts +2 -0
  7. package/dist/file.js +38 -0
  8. package/dist/http.d.ts +43 -0
  9. package/dist/http.js +124 -0
  10. package/dist/index.d.ts +42 -2
  11. package/dist/index.js +51 -17
  12. package/dist/mic.d.ts +27 -0
  13. package/dist/mic.js +236 -0
  14. package/dist/realtime.d.ts +77 -0
  15. package/dist/realtime.js +490 -0
  16. package/dist/resources.d.ts +374 -0
  17. package/dist/resources.js +712 -0
  18. package/dist/subscription.d.ts +38 -0
  19. package/dist/subscription.js +153 -0
  20. package/dist/ws.d.ts +9 -0
  21. package/dist/ws.js +12 -0
  22. package/package.json +32 -36
  23. package/skills/vocalstack/SKILL.md +249 -0
  24. package/dist/ApiVersion.d.ts +0 -2
  25. package/dist/ApiVersion.js +0 -32
  26. package/dist/RestApi.d.ts +0 -21
  27. package/dist/RestApi.js +0 -95
  28. package/dist/VocalStackApi.d.ts +0 -19
  29. package/dist/VocalStackApi.js +0 -60
  30. package/dist/WebsocketApi.d.ts +0 -38
  31. package/dist/WebsocketApi.js +0 -150
  32. package/dist/apis/Billing.d.ts +0 -59
  33. package/dist/apis/Billing.js +0 -21
  34. package/dist/apis/LiveTranscription.d.ts +0 -58
  35. package/dist/apis/LiveTranscription.js +0 -55
  36. package/dist/apis/Polyglot.d.ts +0 -113
  37. package/dist/apis/Polyglot.js +0 -68
  38. package/dist/apis/Security.d.ts +0 -25
  39. package/dist/apis/Security.js +0 -35
  40. package/dist/apis/Transcriptions.d.ts +0 -60
  41. package/dist/apis/Transcriptions.js +0 -38
  42. package/dist/apis/UrlTranscription.d.ts +0 -59
  43. package/dist/apis/UrlTranscription.js +0 -53
  44. package/dist/apis/index.d.ts +0 -6
  45. package/dist/apis/index.js +0 -22
  46. package/dist/esm/ApiVersion.d.ts +0 -2
  47. package/dist/esm/ApiVersion.js +0 -28
  48. package/dist/esm/RestApi.d.ts +0 -21
  49. package/dist/esm/RestApi.js +0 -91
  50. package/dist/esm/VocalStackApi.d.ts +0 -19
  51. package/dist/esm/VocalStackApi.js +0 -56
  52. package/dist/esm/WebsocketApi.d.ts +0 -38
  53. package/dist/esm/WebsocketApi.js +0 -146
  54. package/dist/esm/apis/Billing.d.ts +0 -59
  55. package/dist/esm/apis/Billing.js +0 -17
  56. package/dist/esm/apis/LiveTranscription.d.ts +0 -58
  57. package/dist/esm/apis/LiveTranscription.js +0 -51
  58. package/dist/esm/apis/Polyglot.d.ts +0 -113
  59. package/dist/esm/apis/Polyglot.js +0 -64
  60. package/dist/esm/apis/Security.d.ts +0 -25
  61. package/dist/esm/apis/Security.js +0 -31
  62. package/dist/esm/apis/Transcriptions.d.ts +0 -60
  63. package/dist/esm/apis/Transcriptions.js +0 -34
  64. package/dist/esm/apis/UrlTranscription.d.ts +0 -59
  65. package/dist/esm/apis/UrlTranscription.js +0 -49
  66. package/dist/esm/apis/index.d.ts +0 -6
  67. package/dist/esm/apis/index.js +0 -6
  68. package/dist/esm/generics.zod.d.ts +0 -83
  69. package/dist/esm/generics.zod.js +0 -47
  70. package/dist/esm/index.d.ts +0 -2
  71. package/dist/esm/index.js +0 -2
  72. package/dist/generics.zod.d.ts +0 -83
  73. package/dist/generics.zod.js +0 -50
@@ -0,0 +1,38 @@
1
+ import type { ServerFrame, SubscriberFrame, TranscriptSnapshot } from '@vocalstack/contract';
2
+ export interface SubscribeOptions {
3
+ lang?: string;
4
+ voice?: string;
5
+ listenerToken?: string;
6
+ shareCode?: string;
7
+ asOwner?: boolean;
8
+ audio?: boolean;
9
+ }
10
+ export type SubscriptionEvent = {
11
+ type: 'snapshot';
12
+ snapshot: TranscriptSnapshot;
13
+ reason: 'initial' | 'gap';
14
+ } | {
15
+ type: 'frame';
16
+ frame: ServerFrame | SubscriberFrame;
17
+ seq?: number;
18
+ } | {
19
+ type: 'audio';
20
+ bytes: ArrayBuffer;
21
+ } | {
22
+ type: 'closed';
23
+ reason?: string;
24
+ };
25
+ type Snapshotter = (after: number | undefined) => Promise<TranscriptSnapshot>;
26
+ export declare class Subscription implements AsyncIterable<SubscriptionEvent> {
27
+ #private;
28
+ readonly recordingId: string;
29
+ cursor: number;
30
+ snapshot: TranscriptSnapshot | null;
31
+ private constructor();
32
+ static open(recordingId: string, url: string, bearer: string | undefined, opts: SubscribeOptions, snap: Snapshotter): Promise<Subscription>;
33
+ setLanguage(lang: string | null): void;
34
+ setAudio(enabled: boolean): void;
35
+ close(): void;
36
+ [Symbol.asyncIterator](): AsyncIterator<SubscriptionEvent>;
37
+ }
38
+ export {};
@@ -0,0 +1,153 @@
1
+ import { VocalStackError } from './errors.js';
2
+ import { openWebSocket, WS_OPEN } from './ws.js';
3
+ export class Subscription {
4
+ recordingId;
5
+ cursor = -1;
6
+ snapshot = null;
7
+ #snap;
8
+ #opts;
9
+ #ws;
10
+ #queue = [];
11
+ #wake = null;
12
+ #closed = false;
13
+ #healing = false;
14
+ #buffering = [];
15
+ constructor(recordingId, ws, opts, snap) {
16
+ this.recordingId = recordingId;
17
+ this.#ws = ws;
18
+ this.#opts = opts;
19
+ this.#snap = snap;
20
+ }
21
+ static async open(recordingId, url, bearer, opts, snap) {
22
+ const ws = await openWebSocket(url, bearer);
23
+ ws.binaryType = 'arraybuffer';
24
+ const sub = new Subscription(recordingId, ws, opts, snap);
25
+ await new Promise((resolve, reject) => {
26
+ const timer = setTimeout(() => reject(new VocalStackError('engine_unavailable', 'no subscribe.ready', { retryable: true })), 15_000);
27
+ ws.addEventListener('message', (ev) => {
28
+ if (typeof ev.data !== 'string') {
29
+ sub.#push({ type: 'audio', bytes: ev.data });
30
+ return;
31
+ }
32
+ let frame;
33
+ try {
34
+ frame = JSON.parse(ev.data);
35
+ }
36
+ catch {
37
+ return;
38
+ }
39
+ if (frame.type === 'subscribe.ready') {
40
+ clearTimeout(timer);
41
+ resolve();
42
+ return;
43
+ }
44
+ sub.#onFrame(frame);
45
+ });
46
+ ws.addEventListener('close', (ev) => {
47
+ clearTimeout(timer);
48
+ sub.#push({ type: 'closed', reason: ev?.reason });
49
+ sub.#closed = true;
50
+ sub.#wake?.();
51
+ reject(new VocalStackError('engine_unavailable', 'socket closed before subscribe.ready', { retryable: true }));
52
+ });
53
+ ws.addEventListener('error', () => {
54
+ clearTimeout(timer);
55
+ reject(new VocalStackError('engine_unavailable', 'subscriber socket error', { retryable: true }));
56
+ });
57
+ });
58
+ if (opts.audio)
59
+ sub.setAudio(true);
60
+ await sub.#resnapshot('initial');
61
+ return sub;
62
+ }
63
+ async #resnapshot(reason) {
64
+ const snap = await this.#snap(reason === 'gap' ? this.cursor : undefined);
65
+ this.snapshot = reason === 'initial' || !this.snapshot ? snap : mergeSnapshot(this.snapshot, snap);
66
+ this.cursor = Math.max(this.cursor, snap.lastSeq);
67
+ this.#push({ type: 'snapshot', snapshot: snap, reason });
68
+ const buffered = this.#buffering ?? [];
69
+ this.#buffering = null;
70
+ for (const b of buffered)
71
+ this.#onFrame(b.frame);
72
+ }
73
+ #onFrame(frame) {
74
+ const seq = typeof frame.cursor === 'number' ? frame.cursor : typeof frame.seq === 'number' ? frame.seq : undefined;
75
+ if (this.#buffering) {
76
+ this.#buffering.push({ seq, frame });
77
+ return;
78
+ }
79
+ if (seq !== undefined) {
80
+ if (seq <= this.cursor)
81
+ return;
82
+ if (seq > this.cursor + 1 && !this.#healing) {
83
+ this.#healing = true;
84
+ this.#buffering = [{ seq, frame }];
85
+ this.#resnapshot('gap')
86
+ .catch(() => { })
87
+ .finally(() => {
88
+ this.#healing = false;
89
+ });
90
+ return;
91
+ }
92
+ this.cursor = seq;
93
+ if (frame.type === 'noop')
94
+ return;
95
+ }
96
+ this.#push({ type: 'frame', frame, seq });
97
+ }
98
+ #push(ev) {
99
+ this.#queue.push(ev);
100
+ this.#wake?.();
101
+ }
102
+ setLanguage(lang) {
103
+ this.#opts.lang = lang ?? undefined;
104
+ this.#send({ type: 'subscribe.update', lang: lang ?? 'none' });
105
+ }
106
+ setAudio(enabled) {
107
+ this.#send({ type: 'subscribe.update', audio: enabled });
108
+ }
109
+ #send(frame) {
110
+ if (this.#ws.readyState === WS_OPEN)
111
+ this.#ws.send(JSON.stringify(frame));
112
+ }
113
+ close() {
114
+ if (this.#closed)
115
+ return;
116
+ this.#closed = true;
117
+ try {
118
+ this.#ws.close();
119
+ }
120
+ catch {
121
+ }
122
+ this.#wake?.();
123
+ }
124
+ async *[Symbol.asyncIterator]() {
125
+ for (;;) {
126
+ if (this.#queue.length) {
127
+ yield this.#queue.shift();
128
+ continue;
129
+ }
130
+ if (this.#closed)
131
+ return;
132
+ await new Promise((r) => {
133
+ this.#wake = r;
134
+ });
135
+ this.#wake = null;
136
+ }
137
+ }
138
+ }
139
+ function mergeSnapshot(base, delta) {
140
+ const segs = new Map(base.segments.map((s) => [s.seq, s]));
141
+ for (const s of delta.segments)
142
+ segs.set(s.seq, s);
143
+ const sents = new Map(base.sentences.map((s) => [s.sentenceId, s]));
144
+ for (const s of delta.sentences)
145
+ sents.set(s.sentenceId, s);
146
+ return {
147
+ ...delta,
148
+ segments: [...segs.values()].sort((a, b) => a.seq - b.seq),
149
+ sentences: [...sents.values()].sort((a, b) => a.sentenceId - b.sentenceId),
150
+ speakers: delta.speakers.length ? delta.speakers : base.speakers,
151
+ spans: delta.spans.length ? delta.spans : base.spans,
152
+ };
153
+ }
package/dist/ws.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export interface WsLike {
2
+ binaryType: string;
3
+ readyState: number;
4
+ send(data: string | ArrayBufferLike | Uint8Array): void;
5
+ close(code?: number, reason?: string): void;
6
+ addEventListener(type: string, listener: (ev: any) => void): void;
7
+ }
8
+ export declare const WS_OPEN = 1;
9
+ export declare function openWebSocket(url: string, bearer?: string): Promise<WsLike>;
package/dist/ws.js ADDED
@@ -0,0 +1,12 @@
1
+ export const WS_OPEN = 1;
2
+ const isNode = () => typeof globalThis.process?.versions?.node === 'string' &&
3
+ !globalThis.Deno &&
4
+ !globalThis.Bun;
5
+ export async function openWebSocket(url, bearer) {
6
+ if (!isNode() && typeof globalThis.WebSocket === 'function') {
7
+ const u = bearer ? `${url}${url.includes('?') ? '&' : '?'}access_token=${encodeURIComponent(bearer)}` : url;
8
+ return new globalThis.WebSocket(u);
9
+ }
10
+ const { WebSocket: NodeWebSocket } = await import('ws');
11
+ return new NodeWebSocket(url, bearer ? { headers: { authorization: `Bearer ${bearer}` } } : undefined);
12
+ }
package/package.json CHANGED
@@ -1,48 +1,44 @@
1
1
  {
2
2
  "name": "@vocalstack/js-sdk",
3
- "version": "1.1.0",
4
- "description": "A JavaScript API Client and SDK for VocalStack",
5
- "author": "VocalStack",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+ssh://git@github.com/vocalstack/js-sdk.git"
9
- },
3
+ "version": "2.0.0-alpha.1",
4
+ "description": "VocalStack speech API SDK for Node and browsers: one client (VocalStack) grouped by resource, typed from the generated contract, with realtime WebSocket streaming.",
5
+ "license": "MIT",
6
+ "type": "module",
10
7
  "engines": {
11
- "node": ">=16.20.2",
12
- "npm": ">=8.19.4"
8
+ "node": ">=20"
13
9
  },
14
10
  "main": "./dist/index.js",
15
- "typings": "./dist/index.d.ts",
16
- "module": "./dist/esm/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./mic": {
18
+ "types": "./dist/mic.d.ts",
19
+ "default": "./dist/mic.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "SKILL.md",
25
+ "skills"
26
+ ],
17
27
  "scripts": {
18
- "lint": "eslint",
19
- "build": "rm -rf ./dist && npm i && tsc && tsc -p tsconfig.esm.json",
20
- "docs": "rm -rf ./documentation && npm run --prefix ./../website script:docs -- $(node -e \"console.log(process.cwd())\")",
21
- "prepublish": "npm run docs",
22
- "prepare": "tsc -p tsconfig.esm.json"
28
+ "build": "npm run build --prefix contract && tsc -p tsconfig.json",
29
+ "test": "node --test test/*.test.mjs"
23
30
  },
24
31
  "dependencies": {
25
- "ws": "^8.18.0",
26
- "zod": "^3.23.8"
32
+ "@vocalstack/contract": "1.0.0-alpha.1",
33
+ "ws": "^8.18.0"
27
34
  },
28
35
  "devDependencies": {
29
- "@types/node": "^22.9.0",
30
- "eslint": "^8.57.0",
31
- "eslint-config-prettier": "^9.1.0",
32
- "eslint-plugin-prettier": "^5.1.3",
33
- "typescript": "^5.3.3"
36
+ "@types/node": "^22.0.0",
37
+ "@types/ws": "^8.5.0",
38
+ "typescript": "^5.6.0"
34
39
  },
35
- "publishConfig": {
36
- "access": "restricted"
37
- },
38
- "keywords": [
39
- "vocalstack",
40
- "api",
41
- "sdk",
42
- "transcription",
43
- "livestream",
44
- "ASR",
45
- "speech-to-text",
46
- "speech-to-speech"
47
- ]
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/vocalstack/js-sdk.git"
43
+ }
48
44
  }
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: vocalstack
3
+ description: Use when writing Node or TypeScript code that calls the VocalStack speech API with @vocalstack/js-sdk, for example to transcribe an audio or video URL or a local file, stream live audio for realtime transcription, fetch or export a transcript (JSON, SRT, XLSX, PDF), or search transcripts. Covers installing, API keys, base URLs, five copy-paste programs and error handling (401, 402, 403, 429).
4
+ ---
5
+
6
+ # VocalStack TypeScript SDK
7
+
8
+ The VocalStack API transcribes speech (files, links, live audio), with speaker
9
+ labels and translation. `@vocalstack/js-sdk` has one client, `VocalStack`, with
10
+ one group per resource: `vs.transcriptions`, `vs.jobs`, `vs.recordings`,
11
+ `vs.broadcasts`, `vs.search`, `vs.realtime` and a few more (listed at the end).
12
+
13
+ ## What is what
14
+
15
+ - A **transcription** is a file, a link, a microphone or a stream, transcribed
16
+ and stored. Create one with `vs.transcriptions.create(...)`.
17
+ - A **recording** is one take inside it. Its id (`recordingId`) is what you
18
+ read the transcript and exports with: `vs.recordings.transcript(recordingId)`.
19
+ - A **job** is a file or link recording's transcription work:
20
+ `vs.jobs.wait(recordingId)` polls it until it is `done` or `failed`
21
+ (`{ onProgress: (job) => … }` sees each read: `job.state`, `job.progress.pct`).
22
+ - A **batch** is many files transcribed together for less than one
23
+ transcription each: `vs.batches.create()`, `addFile`/`addUrl` per file,
24
+ `start`, then `wait` (section 6).
25
+ - A **broadcast** is a link listeners open to follow a live recording.
26
+ - A **realtime session** (`vs.realtime`) is a socket you stream audio into and
27
+ read the transcript from. Nothing is stored. "Session" means only this.
28
+
29
+ ## Install
30
+
31
+ ```sh
32
+ npm init -y && npm pkg set type=module # in a new folder: the programs below are ESM
33
+ npm i @vocalstack/js-sdk # Node 20 or newer
34
+ ```
35
+
36
+ To run code newer than the npm release, install it from a clone of
37
+ https://github.com/vocalstack/js-sdk. Build it
38
+ first (`npm run build` also builds the bundled `contract/` package):
39
+
40
+ ```sh
41
+ git clone https://github.com/vocalstack/js-sdk && (cd js-sdk && npm install && npm run build)
42
+ npm i ./js-sdk
43
+ ```
44
+
45
+ Use ESM (`"type": "module"` in package.json, or `.mjs` files).
46
+
47
+ Speaker numbers start at 0. A speaker of `-1` means speaker labels were off
48
+ for that transcription, so print the text without a speaker.
49
+
50
+ ## Auth and base URL
51
+
52
+ - Read the key from `VOCALSTACK_API_KEY` and pass it as `apiKey`. It goes out
53
+ as `Authorization: Bearer <key>`, also on sockets.
54
+ - An API key starts with `vs_key_`. There is one kind of key: every call is
55
+ real usage on the account's tier and quotas.
56
+ - Keys are for servers. Never put a key in browser code or in a URL. The API
57
+ refuses a key sent from a web page (`Origin` header) or in a query string.
58
+ - Scopes: `read` (read transcripts, search, export), `write` and
59
+ `recordings:publish` (create transcriptions, stream live audio), or `*`.
60
+ - Base URL: `https://api.vocalstack.com` (the default). Override it with
61
+ `VOCALSTACK_BASE_URL` (for example, a local or staging server).
62
+
63
+ Every program below starts like this:
64
+
65
+ ```js
66
+ import { VocalStack } from '@vocalstack/js-sdk';
67
+ const vs = new VocalStack({
68
+ baseUrl: process.env.VOCALSTACK_BASE_URL, // absent: https://api.vocalstack.com
69
+ apiKey: process.env.VOCALSTACK_API_KEY,
70
+ });
71
+ ```
72
+
73
+ ## 1. Transcribe a URL and wait for the result
74
+
75
+ The server fetches the link itself, so it must be a public http(s) address.
76
+ Private, loopback and cloud-metadata addresses are refused.
77
+
78
+ ```js
79
+ import { VocalStack } from '@vocalstack/js-sdk';
80
+ const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
81
+
82
+ const tx = await vs.transcriptions.create({
83
+ url: 'https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac',
84
+ name: 'JFK sample',
85
+ });
86
+ const recordingId = tx.job.recordingId;
87
+
88
+ // queued -> fetching -> transcribing -> done | failed
89
+ const job = await vs.jobs.wait(recordingId);
90
+ if (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error}`);
91
+
92
+ const { sentences } = await vs.recordings.transcript(recordingId);
93
+ for (const s of sentences) console.log(s.speaker >= 0 ? `[speaker ${s.speaker}] ${s.text}` : s.text);
94
+ console.log('recordingId', recordingId);
95
+ ```
96
+
97
+ ## 2. Transcribe a local file
98
+
99
+ ```js
100
+ import { VocalStack } from '@vocalstack/js-sdk';
101
+ const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
102
+
103
+ const tx = await vs.transcriptions.create({
104
+ file: process.argv[2], // a path (mp3, wav, flac, ogg, m4a, mp4 ...), a Blob or bytes
105
+ settings: { spokenLanguage: 'en', speakerLabels: 'on' }, // both optional; speakerLabels: 'auto' (default) | 'on' | 'off'
106
+ // Off unless asked for: settings.summary, settings.title, settings.summaryTranslations,
107
+ // settings.voice (spoken translations), and shareLink: true (a public share link).
108
+ });
109
+ const job = await vs.jobs.wait(tx.job.recordingId);
110
+ if (job.state !== 'done') throw new Error(`transcription ${job.state}: ${job.error}`);
111
+
112
+ const { sentences } = await vs.recordings.transcript(job.recordingId);
113
+ console.log(sentences.map((s) => s.text).join(' '));
114
+ ```
115
+
116
+ ## 3. Fetch a transcript or an export file
117
+
118
+ `recordings.transcript` returns segments (with word timings), sentences and
119
+ speakers. `recordings.export` returns the file as a `Response`: formats `json`,
120
+ `srt`, `xlsx`, `pdf`; add `{ lang: '<code>' }` for a translated file.
121
+
122
+ ```js
123
+ import { writeFile } from 'node:fs/promises';
124
+ import { VocalStack } from '@vocalstack/js-sdk';
125
+ const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
126
+ const recordingId = process.argv[2];
127
+
128
+ const snap = await vs.recordings.transcript(recordingId);
129
+ console.log(`${snap.segments.length} segments, ${snap.sentences.length} sentences, state ${snap.state}`);
130
+
131
+ const res = await vs.recordings.export(recordingId, 'srt');
132
+ await writeFile('transcript.srt', Buffer.from(await res.arrayBuffer()));
133
+ ```
134
+
135
+ ## 4. Live transcription from a microphone or a file
136
+
137
+ A live transcription takes raw PCM: 32-bit float little-endian, 16 kHz, mono.
138
+ Create it, start a recording, and open the publisher socket the grant names.
139
+ Here ffmpeg decodes a file at realtime speed (`-re`). For a microphone, swap
140
+ the input: `-f avfoundation -i :0` on macOS, `-f pulse -i default` on Linux.
141
+ (`@vocalstack/js-sdk/mic` is for browsers, and browsers must not hold API keys.)
142
+
143
+ ```js
144
+ import { spawn } from 'node:child_process';
145
+ import { VocalStack } from '@vocalstack/js-sdk';
146
+ const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
147
+
148
+ const tx = await vs.transcriptions.createLive({ name: 'Live demo', settings: { spokenLanguage: 'en' } });
149
+ const grant = await vs.transcriptions.startRecording(tx.id);
150
+ const stream = await vs.recordings.publish(grant, { readyDeadlineMs: 180_000 }); // a cold engine can take a while
151
+
152
+ const ff = spawn('ffmpeg', ['-loglevel', 'error', '-re', '-i', process.argv[2],
153
+ '-f', 'f32le', '-ac', '1', '-ar', '16000', '-']);
154
+ ff.stdout.on('data', (chunk) => stream.sendAudio(new Uint8Array(chunk)));
155
+ ff.on('close', () => stream.eof()); // eof() ends the recording
156
+
157
+ for await (const ev of stream) {
158
+ if (ev.type === 'transcript.committed') console.log(`[speaker ${ev.segment.speaker}] ${ev.segment.text}`);
159
+ // also: 'transcript.draft' (replaces the live tail), 'transcript.sentence', 'transcript.translated'
160
+ }
161
+
162
+ // To end a recording without eof(): vs.recordings.pause(id), then vs.recordings.stop(id).
163
+ const { sentences } = await vs.recordings.transcript(grant.recording.id);
164
+ console.log('final:', sentences.map((s) => s.text).join(' '));
165
+ ```
166
+
167
+ To transcribe without storing anything, use a realtime session instead:
168
+ `await vs.realtime.transcribeFile('talk.mp3')` returns `{ text, segments }`.
169
+
170
+ ## 5. Search transcripts
171
+
172
+ ```js
173
+ import { VocalStack } from '@vocalstack/js-sdk';
174
+ const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
175
+
176
+ const { hits } = await vs.search.query(process.argv[2] ?? 'country', { limit: 5 }); // q: 2+ characters
177
+ for (const h of hits) {
178
+ console.log(`${h.linkName} @ ${h.mediaStart.toFixed(1)}s [speaker ${h.speaker}]: ${h.text} (recording ${h.recordingId})`);
179
+ }
180
+ ```
181
+
182
+ ## 6. A batch of files
183
+
184
+ Many files at once cost less as a batch: one create, one minimum charge for
185
+ the whole batch. Its files get transcripts, exports and speaker labels (when
186
+ `settings.speakerLabels` asks), never a summary, title, share link or voices.
187
+
188
+ ```js
189
+ import { VocalStack } from '@vocalstack/js-sdk';
190
+ const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
191
+
192
+ const batch = await vs.batches.create({ name: 'Interviews', settings: { spokenLanguage: 'en' } });
193
+ for (const path of process.argv.slice(2)) await vs.batches.addFile(batch.id, { file: path }); // or addUrl(batch.id, url)
194
+ await vs.batches.start(batch.id);
195
+ const done = await vs.batches.wait(batch.id); // done (or stopped); check done.counts.failed
196
+ for (const tx of done.files ?? []) {
197
+ if (tx.job?.state !== 'done') continue;
198
+ const { sentences } = await vs.recordings.transcript(tx.job.recordingId);
199
+ console.log(tx.name, sentences.map((s) => s.text).join(' '));
200
+ }
201
+ // vs.batches.export(batch.id, 'srt') is every finished transcript in one zip.
202
+ ```
203
+
204
+ ## Errors
205
+
206
+ A failed call throws `VocalStackError` with `code`, `message`, `status`,
207
+ `retryable`, `details` (the body's `data.details`), `headers` and
208
+ `retryAfter` (seconds, from `Retry-After`). A `429`, and a `503 unavailable`
209
+ on a call that is safe to repeat, is retried for you after its `Retry-After`,
210
+ twice by default (`maxRetries` on the client).
211
+
212
+ | Status | `code` | What to do |
213
+ |---|---|---|
214
+ | 401 | `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`. Use the replacement key, or create a new one. |
215
+ | 403 | `forbidden` | Usually a missing scope: the message names it, and `err.headers.get('www-authenticate')` lists the scopes the route needs. A suspended key or account gets `suspended`. |
216
+ | 403 | `terms_acceptance_required` | The account has not accepted the current Terms of Service and Privacy Policy: someone signs in to the Dashboard and accepts them (an organisation's keys work once an owner or admin has). Not retryable until then. |
217
+ | 402 | `insufficient_credit` | The account's credit is out, or does not cover the transcription: every transcription is billed at least the plan's minimum (`rates.minimumSeconds`, 5 minutes). `details`: `balance`, `plan`, `topUpUrl`, and `required` when the cost was known up front. Add credit; `vs.billing.get()` shows the balance. |
218
+ | 409 | `payment_method_required` | Auto top-up (`vs.billing.setAutoTopUp`) and the portal (`vs.billing.portal()`) need a payment first (`vs.billing.createCheckout({ kind: 'topup', amount, acceptTerms: true })` opens one; a signed-in person's token, never an API key). |
219
+ | 402 | `quota_exceeded` | The plan's transcriptions-at-once limit is reached. `details`: `metric` (`concurrent_transcriptions`), `limit`, `used`, `plan`, `topUpUrl`. |
220
+ | 429 | `rate_limited` | Retried automatically; if it still fails, wait `err.retryAfter` seconds. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
221
+ | 503 | `unavailable` | The service is waking (its database was asleep). Retried automatically after `Retry-After` (2 s) on calls that are safe to repeat: GET, HEAD, DELETE, or any call with `{ idempotencyKey }`. A create without a key is not retried: retry it yourself, with an `idempotencyKey` so it cannot run twice. A realtime socket refused this way throws `unavailable`; a bare 503 there is `capacity_exceeded`. |
222
+
223
+ ```js
224
+ import { VocalStack, VocalStackError } from '@vocalstack/js-sdk';
225
+ const vs = new VocalStack({ baseUrl: process.env.VOCALSTACK_BASE_URL, apiKey: process.env.VOCALSTACK_API_KEY });
226
+
227
+ try {
228
+ await vs.search.query('country');
229
+ } catch (err) {
230
+ if (!(err instanceof VocalStackError)) throw err;
231
+ if (err.code === 'insufficient_credit' || err.code === 'quota_exceeded') console.error(err.message, err.details);
232
+ else console.error(err.status, err.code, err.message);
233
+ }
234
+ ```
235
+
236
+ ## More
237
+
238
+ - The other groups: `vs.transcriptions` (`list`, `get`, `update`, `archive`,
239
+ `restore`, `purge`, `listShareLinks`, `createShareLink`), `vs.recordings`
240
+ (`get`, `summarize`, `summary`, `translation`, `audioUrl`, `subscribe` for a
241
+ live transcript feed), `vs.batches` (`create`, `addFile`, `addUrl`, `start`,
242
+ `wait`, `get`, `list`, `export`, `stop`, `delete`), `vs.corrections`, `vs.shareLinks`, `vs.voices`,
243
+ `vs.broadcasts`, `vs.me` (`get`, `usage`), `vs.languages`,
244
+ `vs.rtmp`, `vs.events`, `vs.meta`. Each method is named after its operation
245
+ in the OpenAPI document (`transcriptions.create` is `POST /v1/transcriptions`).
246
+ - `<base>/llms.txt` lists every operation in a form written for agents.
247
+ `<base>/llms-full.txt` adds parameters, auth mode, tier and scopes.
248
+ - `<base>/docs` is the interactive reference. `<base>/v1/openapi.json` is
249
+ the OpenAPI document; the SDK's types are generated from it.
@@ -1,2 +0,0 @@
1
- export declare const EXPECTED_VOCALSTACK_API_VERSION = "1.1.0";
2
- export declare const verifyApiVersion: (actualVersion: string | null, discontinued?: boolean) => void;
@@ -1,32 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifyApiVersion = exports.EXPECTED_VOCALSTACK_API_VERSION = void 0;
4
- exports.EXPECTED_VOCALSTACK_API_VERSION = '1.1.0';
5
- const verifyApiVersion = (actualVersion, discontinued = false) => {
6
- if (!actualVersion) {
7
- throw new Error('VocalStack API version is missing.');
8
- }
9
- if (discontinued) {
10
- throw Error(`The VocalStack API has undergone a new release (v.${actualVersion}) and no longer supports this SDK. Please update the SDK to the latest version by running 'npm update @vocalstack/js-sdk'.`);
11
- }
12
- const [major, minor, patch] = parseVersion(actualVersion);
13
- const [expectedMajor, expectedMinor, expectedPatch] = exports.EXPECTED_VOCALSTACK_API_VERSION.split('.').map((v) => parseInt(v, 10));
14
- if (major < expectedMajor ||
15
- (major === expectedMajor && minor < expectedMinor) ||
16
- (major === expectedMajor && minor === expectedMinor && patch < expectedPatch)) {
17
- throw new Error(`VocalStack API version ${actualVersion} is lower than that expected by this SDK. Expected version >= ${exports.EXPECTED_VOCALSTACK_API_VERSION}`);
18
- }
19
- if (major > expectedMajor || (major === expectedMajor && minor > expectedMinor)) {
20
- const type = major > expectedMajor ? 'major' : 'minor';
21
- console[type === 'major' ? 'error' : 'warn'](`The VocalStack API has undergone a new ${type} release (v.${actualVersion}). Your SDK version may not support all new API features. Consider updating by running 'npm update @vocalstack/js-sdk'.`);
22
- }
23
- };
24
- exports.verifyApiVersion = verifyApiVersion;
25
- function parseVersion(version) {
26
- const regex = /^\d+\.\d+\.\d+$/;
27
- if (!regex.test(version)) {
28
- throw new Error(`Invalid version format: ${version}. Expected format "major.minor.patch" with numeric segments.`);
29
- }
30
- const segments = version.split('.').map((v) => parseInt(v, 10));
31
- return [segments[0], segments[1], segments[2]];
32
- }
package/dist/RestApi.d.ts DELETED
@@ -1,21 +0,0 @@
1
- export type ApiResponse<T> = {
2
- status: 'waiting' | 'processing' | 'done' | 'error';
3
- data?: null | T;
4
- success: boolean;
5
- error?: string;
6
- };
7
- export type RequestOptions = RequestInit & {
8
- json?: Record<string, unknown>;
9
- };
10
- export declare class RestApi {
11
- private apiServer;
12
- private headers;
13
- constructor(apiServer: string, headers: Record<string, string>);
14
- get<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
15
- post<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
16
- put<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
17
- patch<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
18
- delete<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
19
- private apiCall;
20
- private respond;
21
- }