vocalstack 1.0.0a0__tar.gz

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.
@@ -0,0 +1,9 @@
1
+ # Packaging: hatchling copies the nearest .gitignore into the sdist, so this
2
+ # one stands in for the monorepo root's.
3
+ .venv/
4
+ dist/
5
+ build/
6
+ __pycache__/
7
+ *.egg-info/
8
+ .env
9
+ .env.*
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.5
2
+ Name: vocalstack
3
+ Version: 1.0.0a0
4
+ Summary: VocalStack speech API SDK (asyncio): one client grouped by resource, typed models from the API contract, realtime WebSocket streaming.
5
+ Project-URL: Homepage, https://vocalstack.com
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: pydantic>=2.7
10
+ Requires-Dist: websockets>=13
11
+ Description-Content-Type: text/markdown
12
+
13
+ # vocalstack
14
+
15
+ The VocalStack speech API for Python (3.10 or newer, asyncio): one client,
16
+ `VocalStack`, with one group per resource. Requests go through `httpx`; answers
17
+ are pydantic models generated from the API's one OpenAPI document
18
+ (`/v1/openapi.json`), with snake_case attributes.
19
+
20
+ ```python
21
+ import asyncio, os
22
+ from vocalstack import VocalStack
23
+
24
+ async def main():
25
+ async with VocalStack(api_key=os.environ["VOCALSTACK_API_KEY"]) as vs: # base_url defaults to https://api.vocalstack.com
26
+ tx = await vs.transcriptions.create(url="https://example.com/talk.mp3") # or file="talk.mp3"
27
+ job = await vs.jobs.wait(tx.job.recording_id) # done | failed | cropping
28
+ snap = await vs.recordings.transcript(job.recording_id)
29
+ print(" ".join(s.text for s in snap.sentences))
30
+ open("talk.srt", "wb").write(await vs.recordings.export(job.recording_id, "srt"))
31
+
32
+ asyncio.run(main())
33
+ ```
34
+
35
+ <!-- sdk-docs: generated by `make sdk-docs` from the guides; edit those, not this -->
36
+
37
+ ## Install
38
+
39
+ Not on PyPI yet. The API serves its own builds to every account: [https://api.vocalstack.com/docs/guides/quickstart.md](https://api.vocalstack.com/docs/guides/quickstart.md) has the install command for the current build (it sends your API key to that host only).
40
+
41
+ ## Guides
42
+
43
+ - [Quickstart](https://github.com/vocalstack/python-sdk/blob/main/docs/quickstart.md): An API key, the SDK, and your first transcript in a dozen lines.
44
+ - [Transcribe a file](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-file.md): Upload a file, wait for its job, read the transcript and export it. Asynchronous; nothing to keep open.
45
+ - [Transcribe a link](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-link.md): The server fetches a public URL or a YouTube video, then transcribes it like a file. Asynchronous, with a fetch phase first.
46
+ - [Stream a microphone](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-microphone.md): Live audio in, transcript events out, over one WebSocket. Drafts replace the live tail; commits append.
47
+ - [Stream a file at full speed](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-file.md): Send a file's bytes through a realtime session and read the transcript as fast as the server makes it. Nothing is stored.
48
+ - [Go live](https://github.com/vocalstack/python-sdk/blob/main/docs/go-live.md): Publish live audio to a stored transcription, and put it on a broadcast link listeners open. Control is HTTP; audio is one WebSocket.
49
+ - [Listen](https://github.com/vocalstack/python-sdk/blob/main/docs/listen.md): Follow a recording as it happens, in any language: subscribe, catch up from a snapshot, then apply live frames in order.
50
+ - [Live translation and spoken translations](https://github.com/vocalstack/python-sdk/blob/main/docs/translation-and-voices.md): Translate a stream as it is spoken, and have each translated sentence spoken back in a voice. Text frames and audio clips, over the same socket.
51
+ - [Errors, retries and limits](https://github.com/vocalstack/python-sdk/blob/main/docs/errors-and-limits.md): One error envelope everywhere; what to retry (429, 503 unavailable) and how to make a retry safe (Idempotency-Key); credit, plan limits and the 402s.
52
+
53
+ <!-- /sdk-docs -->
54
+
55
+ ## What is what
56
+
57
+ | Name | What it is | Group |
58
+ |---|---|---|
59
+ | realtime session | a signed token plus one socket; nothing stored | `vs.realtime` |
60
+ | broadcast | the link listeners open | `vs.broadcasts` |
61
+ | transcription | a file, link, microphone or stream, transcribed and stored | `vs.transcriptions` |
62
+ | recording | one take inside a broadcast or a transcription | `vs.recordings` |
63
+ | job | a file or link recording's transcription work | `vs.jobs` |
64
+
65
+ The other groups: `vs.corrections`, `vs.share_links`, `vs.voices`,
66
+ `vs.search`, `vs.me`, `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
67
+ Method names are the document's `operationId`s in snake_case
68
+ (`broadcasts.start_recording` is `POST /v1/broadcasts/{id}/recordings`).
69
+ Request fields are keyword arguments, in snake_case or the wire's camelCase
70
+ (`settings={"spokenLanguage": "en"}`); lists return lists; binary answers
71
+ (`recordings.export`, `recordings.clip`, `voices.sample`, ...) return bytes.
72
+
73
+ ```python
74
+ VocalStack(
75
+ base_url=None, # default https://api.vocalstack.com
76
+ api_key=None, # an API key (vs_key_...), for a server
77
+ token=None, # or: an access token / browser token, or a callable returning one
78
+ acting_principal=None, # act for an organization (X-Acting-Principal)
79
+ device_id=None,
80
+ max_retries=2, # 429s, and 503 unavailable on safe calls, retried after Retry-After
81
+ timeout=300.0,
82
+ ready_deadline_s=30.0, # how long a socket may take to report ready
83
+ )
84
+ ```
85
+
86
+ ## Realtime sessions and live recordings
87
+
88
+ ```python
89
+ async with VocalStack(api_key=key) as vs:
90
+ # Nothing stored: a file through one socket.
91
+ result = await vs.realtime.transcribe_file("meeting.mp3", spoken_language="en")
92
+ print(result.text)
93
+
94
+ # A stored live transcription: start a recording, stream raw PCM
95
+ # (f32le, 16 kHz, mono) into the publisher socket.
96
+ tx = await vs.transcriptions.create_live(name="Standup")
97
+ grant = await vs.transcriptions.start_recording(tx.id)
98
+ async with await vs.recordings.publish(grant) as stream:
99
+ asyncio.create_task(pace_realtime(stream, pcm)) # then eof()
100
+ async for event in stream:
101
+ if event.type == "transcript.committed":
102
+ print(event.segment.speaker, event.segment.text)
103
+ ```
104
+
105
+ - `transcript.draft` frames **fully replace** the live tail; never append them.
106
+ - `transcript.committed` segments are immutable, gapless by `segment.seq`,
107
+ and carry word timings.
108
+ - A dropped connection ends the socket (no resume): start a new one.
109
+
110
+ ## Errors
111
+
112
+ `VocalStackError` carries `code` (the contract's closed enum), `message`,
113
+ `status`, `retryable`, `details` (quota numbers, code suggestions), `headers`
114
+ and `retry_after`.
115
+
116
+ Verified against the language-agnostic conformance suite (`make conformance`)
117
+ and against a running API (`make sdk-e2e`). For agents: `SKILL.md`.
@@ -0,0 +1,105 @@
1
+ # vocalstack
2
+
3
+ The VocalStack speech API for Python (3.10 or newer, asyncio): one client,
4
+ `VocalStack`, with one group per resource. Requests go through `httpx`; answers
5
+ are pydantic models generated from the API's one OpenAPI document
6
+ (`/v1/openapi.json`), with snake_case attributes.
7
+
8
+ ```python
9
+ import asyncio, os
10
+ from vocalstack import VocalStack
11
+
12
+ async def main():
13
+ async with VocalStack(api_key=os.environ["VOCALSTACK_API_KEY"]) as vs: # base_url defaults to https://api.vocalstack.com
14
+ tx = await vs.transcriptions.create(url="https://example.com/talk.mp3") # or file="talk.mp3"
15
+ job = await vs.jobs.wait(tx.job.recording_id) # done | failed | cropping
16
+ snap = await vs.recordings.transcript(job.recording_id)
17
+ print(" ".join(s.text for s in snap.sentences))
18
+ open("talk.srt", "wb").write(await vs.recordings.export(job.recording_id, "srt"))
19
+
20
+ asyncio.run(main())
21
+ ```
22
+
23
+ <!-- sdk-docs: generated by `make sdk-docs` from the guides; edit those, not this -->
24
+
25
+ ## Install
26
+
27
+ Not on PyPI yet. The API serves its own builds to every account: [https://api.vocalstack.com/docs/guides/quickstart.md](https://api.vocalstack.com/docs/guides/quickstart.md) has the install command for the current build (it sends your API key to that host only).
28
+
29
+ ## Guides
30
+
31
+ - [Quickstart](https://github.com/vocalstack/python-sdk/blob/main/docs/quickstart.md): An API key, the SDK, and your first transcript in a dozen lines.
32
+ - [Transcribe a file](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-file.md): Upload a file, wait for its job, read the transcript and export it. Asynchronous; nothing to keep open.
33
+ - [Transcribe a link](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-link.md): The server fetches a public URL or a YouTube video, then transcribes it like a file. Asynchronous, with a fetch phase first.
34
+ - [Stream a microphone](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-microphone.md): Live audio in, transcript events out, over one WebSocket. Drafts replace the live tail; commits append.
35
+ - [Stream a file at full speed](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-file.md): Send a file's bytes through a realtime session and read the transcript as fast as the server makes it. Nothing is stored.
36
+ - [Go live](https://github.com/vocalstack/python-sdk/blob/main/docs/go-live.md): Publish live audio to a stored transcription, and put it on a broadcast link listeners open. Control is HTTP; audio is one WebSocket.
37
+ - [Listen](https://github.com/vocalstack/python-sdk/blob/main/docs/listen.md): Follow a recording as it happens, in any language: subscribe, catch up from a snapshot, then apply live frames in order.
38
+ - [Live translation and spoken translations](https://github.com/vocalstack/python-sdk/blob/main/docs/translation-and-voices.md): Translate a stream as it is spoken, and have each translated sentence spoken back in a voice. Text frames and audio clips, over the same socket.
39
+ - [Errors, retries and limits](https://github.com/vocalstack/python-sdk/blob/main/docs/errors-and-limits.md): One error envelope everywhere; what to retry (429, 503 unavailable) and how to make a retry safe (Idempotency-Key); credit, plan limits and the 402s.
40
+
41
+ <!-- /sdk-docs -->
42
+
43
+ ## What is what
44
+
45
+ | Name | What it is | Group |
46
+ |---|---|---|
47
+ | realtime session | a signed token plus one socket; nothing stored | `vs.realtime` |
48
+ | broadcast | the link listeners open | `vs.broadcasts` |
49
+ | transcription | a file, link, microphone or stream, transcribed and stored | `vs.transcriptions` |
50
+ | recording | one take inside a broadcast or a transcription | `vs.recordings` |
51
+ | job | a file or link recording's transcription work | `vs.jobs` |
52
+
53
+ The other groups: `vs.corrections`, `vs.share_links`, `vs.voices`,
54
+ `vs.search`, `vs.me`, `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
55
+ Method names are the document's `operationId`s in snake_case
56
+ (`broadcasts.start_recording` is `POST /v1/broadcasts/{id}/recordings`).
57
+ Request fields are keyword arguments, in snake_case or the wire's camelCase
58
+ (`settings={"spokenLanguage": "en"}`); lists return lists; binary answers
59
+ (`recordings.export`, `recordings.clip`, `voices.sample`, ...) return bytes.
60
+
61
+ ```python
62
+ VocalStack(
63
+ base_url=None, # default https://api.vocalstack.com
64
+ api_key=None, # an API key (vs_key_...), for a server
65
+ token=None, # or: an access token / browser token, or a callable returning one
66
+ acting_principal=None, # act for an organization (X-Acting-Principal)
67
+ device_id=None,
68
+ max_retries=2, # 429s, and 503 unavailable on safe calls, retried after Retry-After
69
+ timeout=300.0,
70
+ ready_deadline_s=30.0, # how long a socket may take to report ready
71
+ )
72
+ ```
73
+
74
+ ## Realtime sessions and live recordings
75
+
76
+ ```python
77
+ async with VocalStack(api_key=key) as vs:
78
+ # Nothing stored: a file through one socket.
79
+ result = await vs.realtime.transcribe_file("meeting.mp3", spoken_language="en")
80
+ print(result.text)
81
+
82
+ # A stored live transcription: start a recording, stream raw PCM
83
+ # (f32le, 16 kHz, mono) into the publisher socket.
84
+ tx = await vs.transcriptions.create_live(name="Standup")
85
+ grant = await vs.transcriptions.start_recording(tx.id)
86
+ async with await vs.recordings.publish(grant) as stream:
87
+ asyncio.create_task(pace_realtime(stream, pcm)) # then eof()
88
+ async for event in stream:
89
+ if event.type == "transcript.committed":
90
+ print(event.segment.speaker, event.segment.text)
91
+ ```
92
+
93
+ - `transcript.draft` frames **fully replace** the live tail; never append them.
94
+ - `transcript.committed` segments are immutable, gapless by `segment.seq`,
95
+ and carry word timings.
96
+ - A dropped connection ends the socket (no resume): start a new one.
97
+
98
+ ## Errors
99
+
100
+ `VocalStackError` carries `code` (the contract's closed enum), `message`,
101
+ `status`, `retryable`, `details` (quota numbers, code suggestions), `headers`
102
+ and `retry_after`.
103
+
104
+ Verified against the language-agnostic conformance suite (`make conformance`)
105
+ and against a running API (`make sdk-e2e`). For agents: `SKILL.md`.
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: vocalstack
3
+ description: Use when writing Python code that calls the VocalStack speech API with the vocalstack package, 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 Python SDK
7
+
8
+ The VocalStack API transcribes speech (files, links, live audio), with speaker
9
+ labels and translation. The `vocalstack` package has one asyncio client,
10
+ `VocalStack`, with one group per resource: `vs.transcriptions`, `vs.jobs`,
11
+ `vs.recordings`, `vs.broadcasts`, `vs.search`, `vs.realtime` and a few more
12
+ (listed at the end). Answers are pydantic models with snake_case attributes.
13
+
14
+ ## What is what
15
+
16
+ - A **transcription** is a file, a link, a microphone or a stream, transcribed
17
+ and stored. Create one with `await vs.transcriptions.create(...)`.
18
+ - A **recording** is one take inside it. Its id (`recording_id`) is what you
19
+ read the transcript and exports with: `await vs.recordings.transcript(recording_id)`.
20
+ - A **job** is a file or link recording's transcription work:
21
+ `await vs.jobs.wait(recording_id)` polls it until it is `done` or `failed`
22
+ (`on_progress=lambda job: ...` sees each read: `job.state`, `job.progress.pct`).
23
+ - A **broadcast** is a link listeners open to follow a live recording.
24
+ - A **realtime session** (`vs.realtime`) is a socket you stream audio into and
25
+ read the transcript from. Nothing is stored. "Session" means only this.
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ python3 -m venv .venv && . .venv/bin/activate # system Pythons refuse a bare pip install
31
+ pip install vocalstack # Python 3.10 or newer
32
+ ```
33
+
34
+ Before the package is published, any account installs
35
+ the private build its environment serves: the API reference's Install section
36
+ (`<base>/docs`, or Developers -> API reference in the app) and `<base>/sdk/latest`
37
+ give the exact commands, which send `VOCALSTACK_API_KEY` to that host only.
38
+ Or install it from a local checkout of the repository:
39
+
40
+ ```sh
41
+ pip install -e /absolute/path/to/transcribe/sdks/python
42
+ ```
43
+
44
+ Live transcription (program 4) also needs `ffmpeg` on PATH.
45
+
46
+ Speaker numbers start at 0. A speaker of `-1` means speaker labels were off
47
+ for that transcription, so print the text without a speaker.
48
+
49
+ ## Auth and base URL
50
+
51
+ - Read the key from `VOCALSTACK_API_KEY` and pass it as `api_key`. It goes
52
+ out as `Authorization: Bearer <key>`, also on sockets.
53
+ - An API key starts with `vs_key_`. There is one kind of key: every call is
54
+ real usage on the account's tier and quotas.
55
+ - Keys are for servers. Never put a key in browser code or in a URL. The API
56
+ refuses a key sent from a web page (`Origin` header) or in a query string.
57
+ - Scopes: `read` (read transcripts, search, export), `write` and
58
+ `recordings:publish` (create transcriptions, stream live audio), or `*`.
59
+ - Base URL: `https://api.vocalstack.com` (the default). Override it with
60
+ `VOCALSTACK_BASE_URL`: `http://127.0.0.1:8090` for a local server,
61
+ `https://api.e2e.vocalstack.com` for e2e.
62
+
63
+ Every program below opens the client the same way, inside `async def main()`
64
+ run with `asyncio.run(main())` (a `None` base URL means the default):
65
+
66
+ ```python
67
+ async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
68
+ ...
69
+ ```
70
+
71
+ ## 1. Transcribe a URL and wait for the result
72
+
73
+ The server fetches the link itself, so it must be a public http(s) address.
74
+ Private, loopback and cloud-metadata addresses are refused.
75
+
76
+ ```python
77
+ import asyncio, os
78
+ from vocalstack import VocalStack
79
+
80
+ async def main():
81
+ async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
82
+ tx = await vs.transcriptions.create(
83
+ url="https://raw.githubusercontent.com/openai/whisper/main/tests/jfk.flac",
84
+ name="JFK sample",
85
+ )
86
+ rec = tx.job.recording_id
87
+ job = await vs.jobs.wait(rec) # queued -> fetching -> transcribing -> done | failed
88
+ if job.state != "done":
89
+ raise SystemExit(f"transcription {job.state}: {job.error}")
90
+ snap = await vs.recordings.transcript(rec)
91
+ for s in snap.sentences:
92
+ print(f"[speaker {s.speaker}] {s.text}" if s.speaker >= 0 else s.text)
93
+ print("recording_id", rec)
94
+
95
+ asyncio.run(main())
96
+ ```
97
+
98
+ ## 2. Transcribe a local file
99
+
100
+ ```python
101
+ import asyncio, os, sys
102
+ from vocalstack import VocalStack
103
+
104
+ async def main(path): # mp3, wav, flac, ogg, m4a, mp4 ...
105
+ async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
106
+ tx = await vs.transcriptions.create(
107
+ file=path, # a path, bytes, or an open binary file
108
+ settings={"spokenLanguage": "en", "speakerLabels": "on"}, # both optional; "auto" | "on" | "off"
109
+ )
110
+ job = await vs.jobs.wait(tx.job.recording_id)
111
+ if job.state != "done":
112
+ raise SystemExit(f"transcription {job.state}: {job.error}")
113
+ snap = await vs.recordings.transcript(job.recording_id)
114
+ print(" ".join(s.text for s in snap.sentences))
115
+
116
+ asyncio.run(main(sys.argv[1]))
117
+ ```
118
+
119
+ ## 3. Fetch a transcript or an export file
120
+
121
+ The transcript holds segments (with word timings), sentences and speakers.
122
+ Export formats are `json`, `srt`, `xlsx` and `pdf`; add `lang="<code>"` for a
123
+ translated file. `export` returns the file's bytes.
124
+
125
+ ```python
126
+ import asyncio, os, sys
127
+ from vocalstack import VocalStack
128
+
129
+ async def main(rec):
130
+ async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
131
+ snap = await vs.recordings.transcript(rec)
132
+ print(len(snap.segments), "segments,", len(snap.sentences), "sentences, state", snap.state)
133
+ with open("transcript.srt", "wb") as f:
134
+ f.write(await vs.recordings.export(rec, "srt"))
135
+
136
+ asyncio.run(main(sys.argv[1]))
137
+ ```
138
+
139
+ ## 4. Live transcription from a microphone or a file
140
+
141
+ A live transcription takes raw PCM: 32-bit float little-endian, 16 kHz, mono.
142
+ Create it, start a recording, and open the publisher socket the grant names.
143
+ Here ffmpeg decodes a file, and `pace_realtime` sends it at realtime speed and
144
+ then calls `eof()`. For a microphone, read ffmpeg's stdout as it arrives
145
+ (`-f avfoundation -i :0` on macOS, `-f pulse -i default` on Linux) and call
146
+ `await stream.send_audio(chunk)` for each chunk.
147
+
148
+ ```python
149
+ import asyncio, os, subprocess, sys
150
+ from vocalstack import VocalStack, pace_realtime
151
+
152
+ async def main(path):
153
+ async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
154
+ tx = await vs.transcriptions.create_live(name="Live demo", settings={"spokenLanguage": "en"})
155
+ grant = await vs.transcriptions.start_recording(tx.id)
156
+
157
+ pcm = subprocess.run(["ffmpeg", "-loglevel", "error", "-i", path,
158
+ "-f", "f32le", "-ac", "1", "-ar", "16000", "-"],
159
+ check=True, capture_output=True).stdout
160
+
161
+ # A cold engine can take a while to load, so allow a long ready deadline.
162
+ async with await vs.recordings.publish(grant, ready_deadline_s=180) as stream:
163
+ feeder = asyncio.create_task(pace_realtime(stream, pcm)) # then eof(), which ends the recording
164
+ async for event in stream:
165
+ if event.type == "transcript.committed":
166
+ print(f"[speaker {event.segment.speaker}] {event.segment.text}", flush=True)
167
+ await feeder
168
+
169
+ # To end a recording without eof(): vs.recordings.pause(id), then vs.recordings.stop(id).
170
+ snap = await vs.recordings.transcript(grant.recording.id)
171
+ print("final:", " ".join(s.text for s in snap.sentences))
172
+
173
+ asyncio.run(main(sys.argv[1]))
174
+ ```
175
+
176
+ To transcribe without storing anything, use a realtime session instead:
177
+ `(await vs.realtime.transcribe_file("talk.mp3")).text`.
178
+
179
+ ## 5. Search transcripts
180
+
181
+ ```python
182
+ import asyncio, os, sys
183
+ from vocalstack import VocalStack
184
+
185
+ async def main(q): # 2+ characters
186
+ async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
187
+ res = await vs.search.query(q, limit=5)
188
+ for h in res.hits:
189
+ print(f"{h.link_name} @ {h.media_start:.1f}s [speaker {h.speaker}]: {h.text} (recording {h.recording_id})")
190
+
191
+ asyncio.run(main(sys.argv[1]))
192
+ ```
193
+
194
+ ## Errors
195
+
196
+ A failed call raises `vocalstack.VocalStackError` with `.code`, `.message`,
197
+ `.status`, `.retryable`, `.details` (the body's `data.details`), `.headers`
198
+ and `.retry_after` (seconds, from `Retry-After`). A `429`, and a
199
+ `503 unavailable` on a call that is safe to repeat, is retried for you after
200
+ its `Retry-After`, twice by default (`max_retries` on the client).
201
+
202
+ | Status | `code` | What to do |
203
+ |---|---|---|
204
+ | 401 | `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`. Use the replacement key, or create a new one. |
205
+ | 403 | `forbidden` | Usually a missing scope: the message names it, and `e.headers["www-authenticate"]` lists the scopes the route needs. A suspended key or account gets `suspended`. |
206
+ | 402 | `insufficient_credit` | The account's credit is out. `details`: `balance`, `plan`, `topUpUrl`, and `required` when the cost was known up front. Add credit; `await vs.billing.get()` shows the balance. |
207
+ | 409 | `payment_method_required` | Auto top-up (`vs.billing.set_auto_top_up`) and the portal (`vs.billing.portal()`) need a payment first (`vs.billing.create_checkout(kind="topup", amount=25, accept_terms=True, waive_withdrawal=True)` opens one; a signed-in person's token, never an API key). |
208
+ | 402 | `quota_exceeded` | The plan's transcriptions-at-once limit is reached. `details`: `metric` (`concurrent_transcriptions`), `limit`, `used`, `plan`, `topUpUrl`. |
209
+ | 429 | `rate_limited` | Retried automatically; if it still fails, wait `e.retry_after` seconds. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
210
+ | 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 `idempotency_key=`. A create without a key is not retried: retry it yourself, with an `idempotency_key` so it cannot run twice. A realtime socket refused this way raises `unavailable`; a bare 503 there is `capacity_exceeded`. |
211
+
212
+ ```python
213
+ from vocalstack import VocalStackError
214
+ try:
215
+ await vs.search.query("country")
216
+ except VocalStackError as e:
217
+ if e.code in ("insufficient_credit", "quota_exceeded"):
218
+ print("billing:", e.message, e.details)
219
+ else:
220
+ print(e.status, e.code, e.message)
221
+ ```
222
+
223
+ ## More
224
+
225
+ - The other groups: `vs.transcriptions` (`list`, `get`, `update`, `archive`,
226
+ `restore`, `purge`, `list_share_links`, `create_share_link`), `vs.recordings`
227
+ (`get`, `summarize`, `summary`, `translation`, `audio_url`, `subscribe`),
228
+ `vs.corrections`, `vs.share_links`, `vs.voices`, `vs.broadcasts`,
229
+ `vs.me` (`get`, `usage`), `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
230
+ Each method is its operation's name in the OpenAPI document, in snake_case
231
+ (`transcriptions.create` is `POST /v1/transcriptions`).
232
+ - `<base>/llms.txt` lists every operation in a form written for agents.
233
+ `<base>/llms-full.txt` adds parameters, auth mode, tier and scopes.
234
+ - `<base>/docs` is the interactive reference. `<base>/v1/openapi.json` is
235
+ the OpenAPI document; the SDK's models are generated from it.
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "vocalstack"
7
+ version = "1.0.0a0"
8
+ description = "VocalStack speech API SDK (asyncio): one client grouped by resource, typed models from the API contract, realtime WebSocket streaming."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "httpx>=0.27",
14
+ "websockets>=13",
15
+ "pydantic>=2.7",
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://vocalstack.com"
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/vocalstack"]
23
+
24
+ # The Agent Skill ships inside the package (site-packages/vocalstack/SKILL.md)
25
+ # so an agent can find it next to the code.
26
+ [tool.hatch.build.targets.wheel.force-include]
27
+ "SKILL.md" = "vocalstack/SKILL.md"
28
+
29
+ # An explicit list: nothing else from the repository goes into the sdist.
30
+ [tool.hatch.build.targets.sdist]
31
+ only-include = ["src/vocalstack", "README.md", "SKILL.md", "skills", "pyproject.toml"]