vocalstack 1.0.0a0__tar.gz → 1.0.0a1__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.
- vocalstack-1.0.0a1/.gitignore +8 -0
- vocalstack-1.0.0a1/LICENSE +21 -0
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/PKG-INFO +10 -6
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/README.md +7 -4
- {vocalstack-1.0.0a0/skills/vocalstack → vocalstack-1.0.0a1}/SKILL.md +4 -14
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/pyproject.toml +5 -4
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1/skills/vocalstack}/SKILL.md +4 -14
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/src/vocalstack/__init__.py +1 -1
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/src/vocalstack/_http.py +5 -3
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/src/vocalstack/_models.py +142 -13
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/src/vocalstack/client.py +2 -0
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/src/vocalstack/errors.py +14 -0
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/src/vocalstack/resources.py +57 -8
- vocalstack-1.0.0a0/.gitignore +0 -9
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a1}/src/vocalstack/realtime.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VocalStack
|
|
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.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vocalstack
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a1
|
|
4
4
|
Summary: VocalStack speech API SDK (asyncio): one client grouped by resource, typed models from the API contract, realtime WebSocket streaming.
|
|
5
5
|
Project-URL: Homepage, https://vocalstack.com
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
7
8
|
Requires-Python: >=3.10
|
|
8
9
|
Requires-Dist: httpx>=0.27
|
|
9
10
|
Requires-Dist: pydantic>=2.7
|
|
@@ -32,11 +33,13 @@ async def main():
|
|
|
32
33
|
asyncio.run(main())
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
<!-- sdk-docs: generated
|
|
36
|
+
<!-- sdk-docs: generated from the VocalStack API guides; edits here are overwritten -->
|
|
36
37
|
|
|
37
38
|
## Install
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
```sh
|
|
41
|
+
pip install vocalstack
|
|
42
|
+
```
|
|
40
43
|
|
|
41
44
|
## Guides
|
|
42
45
|
|
|
@@ -48,6 +51,7 @@ Not on PyPI yet. The API serves its own builds to every account: [https://api.vo
|
|
|
48
51
|
- [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
52
|
- [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
53
|
- [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.
|
|
54
|
+
- [Connect an app with one click](https://github.com/vocalstack/python-sdk/blob/main/docs/connect-an-app.md): Let the people who use your app connect their VocalStack account with one Authenticate click — OAuth 2.0 authorization code with PKCE, where the access token is an API key — instead of pasting a key.
|
|
51
55
|
- [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
56
|
|
|
53
57
|
<!-- /sdk-docs -->
|
|
@@ -113,5 +117,5 @@ async with VocalStack(api_key=key) as vs:
|
|
|
113
117
|
`status`, `retryable`, `details` (quota numbers, code suggestions), `headers`
|
|
114
118
|
and `retry_after`.
|
|
115
119
|
|
|
116
|
-
Verified against
|
|
117
|
-
and against a running API
|
|
120
|
+
Verified against a language-agnostic conformance suite shared by every
|
|
121
|
+
VocalStack SDK and against a running API. For agents: `SKILL.md`.
|
|
@@ -20,11 +20,13 @@ async def main():
|
|
|
20
20
|
asyncio.run(main())
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
<!-- sdk-docs: generated
|
|
23
|
+
<!-- sdk-docs: generated from the VocalStack API guides; edits here are overwritten -->
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
```sh
|
|
28
|
+
pip install vocalstack
|
|
29
|
+
```
|
|
28
30
|
|
|
29
31
|
## Guides
|
|
30
32
|
|
|
@@ -36,6 +38,7 @@ Not on PyPI yet. The API serves its own builds to every account: [https://api.vo
|
|
|
36
38
|
- [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
39
|
- [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
40
|
- [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.
|
|
41
|
+
- [Connect an app with one click](https://github.com/vocalstack/python-sdk/blob/main/docs/connect-an-app.md): Let the people who use your app connect their VocalStack account with one Authenticate click — OAuth 2.0 authorization code with PKCE, where the access token is an API key — instead of pasting a key.
|
|
39
42
|
- [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
43
|
|
|
41
44
|
<!-- /sdk-docs -->
|
|
@@ -101,5 +104,5 @@ async with VocalStack(api_key=key) as vs:
|
|
|
101
104
|
`status`, `retryable`, `details` (quota numbers, code suggestions), `headers`
|
|
102
105
|
and `retry_after`.
|
|
103
106
|
|
|
104
|
-
Verified against
|
|
105
|
-
and against a running API
|
|
107
|
+
Verified against a language-agnostic conformance suite shared by every
|
|
108
|
+
VocalStack SDK and against a running API. For agents: `SKILL.md`.
|
|
@@ -31,16 +31,6 @@ python3 -m venv .venv && . .venv/bin/activate # system Pythons refuse a bare p
|
|
|
31
31
|
pip install vocalstack # Python 3.10 or newer
|
|
32
32
|
```
|
|
33
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
34
|
Live transcription (program 4) also needs `ffmpeg` on PATH.
|
|
45
35
|
|
|
46
36
|
Speaker numbers start at 0. A speaker of `-1` means speaker labels were off
|
|
@@ -57,8 +47,7 @@ for that transcription, so print the text without a speaker.
|
|
|
57
47
|
- Scopes: `read` (read transcripts, search, export), `write` and
|
|
58
48
|
`recordings:publish` (create transcriptions, stream live audio), or `*`.
|
|
59
49
|
- Base URL: `https://api.vocalstack.com` (the default). Override it with
|
|
60
|
-
`VOCALSTACK_BASE_URL
|
|
61
|
-
`https://api.e2e.vocalstack.com` for e2e.
|
|
50
|
+
`VOCALSTACK_BASE_URL` (for example, a local or staging server).
|
|
62
51
|
|
|
63
52
|
Every program below opens the client the same way, inside `async def main()`
|
|
64
53
|
run with `asyncio.run(main())` (a `None` base URL means the default):
|
|
@@ -203,8 +192,9 @@ its `Retry-After`, twice by default (`max_retries` on the client).
|
|
|
203
192
|
|---|---|---|
|
|
204
193
|
| 401 | `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`. Use the replacement key, or create a new one. |
|
|
205
194
|
| 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
|
-
|
|
|
207
|
-
|
|
|
195
|
+
| 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. |
|
|
196
|
+
| 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; `await vs.billing.get()` shows the balance. |
|
|
197
|
+
| 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)` opens one; a signed-in person's token, never an API key). |
|
|
208
198
|
| 402 | `quota_exceeded` | The plan's transcriptions-at-once limit is reached. `details`: `metric` (`concurrent_transcriptions`), `limit`, `used`, `plan`, `topUpUrl`. |
|
|
209
199
|
| 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
200
|
| 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`. |
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vocalstack"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0a1"
|
|
8
8
|
description = "VocalStack speech API SDK (asyncio): one client grouped by resource, typed models from the API contract, realtime WebSocket streaming."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
license =
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
11
12
|
requires-python = ">=3.10"
|
|
12
13
|
dependencies = [
|
|
13
14
|
"httpx>=0.27",
|
|
@@ -28,4 +29,4 @@ packages = ["src/vocalstack"]
|
|
|
28
29
|
|
|
29
30
|
# An explicit list: nothing else from the repository goes into the sdist.
|
|
30
31
|
[tool.hatch.build.targets.sdist]
|
|
31
|
-
only-include = ["src/vocalstack", "README.md", "SKILL.md", "skills", "pyproject.toml"]
|
|
32
|
+
only-include = ["src/vocalstack", "LICENSE", "README.md", "SKILL.md", "skills", "pyproject.toml"]
|
|
@@ -31,16 +31,6 @@ python3 -m venv .venv && . .venv/bin/activate # system Pythons refuse a bare p
|
|
|
31
31
|
pip install vocalstack # Python 3.10 or newer
|
|
32
32
|
```
|
|
33
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
34
|
Live transcription (program 4) also needs `ffmpeg` on PATH.
|
|
45
35
|
|
|
46
36
|
Speaker numbers start at 0. A speaker of `-1` means speaker labels were off
|
|
@@ -57,8 +47,7 @@ for that transcription, so print the text without a speaker.
|
|
|
57
47
|
- Scopes: `read` (read transcripts, search, export), `write` and
|
|
58
48
|
`recordings:publish` (create transcriptions, stream live audio), or `*`.
|
|
59
49
|
- Base URL: `https://api.vocalstack.com` (the default). Override it with
|
|
60
|
-
`VOCALSTACK_BASE_URL
|
|
61
|
-
`https://api.e2e.vocalstack.com` for e2e.
|
|
50
|
+
`VOCALSTACK_BASE_URL` (for example, a local or staging server).
|
|
62
51
|
|
|
63
52
|
Every program below opens the client the same way, inside `async def main()`
|
|
64
53
|
run with `asyncio.run(main())` (a `None` base URL means the default):
|
|
@@ -203,8 +192,9 @@ its `Retry-After`, twice by default (`max_retries` on the client).
|
|
|
203
192
|
|---|---|---|
|
|
204
193
|
| 401 | `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`. Use the replacement key, or create a new one. |
|
|
205
194
|
| 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
|
-
|
|
|
207
|
-
|
|
|
195
|
+
| 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. |
|
|
196
|
+
| 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; `await vs.billing.get()` shows the balance. |
|
|
197
|
+
| 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)` opens one; a signed-in person's token, never an API key). |
|
|
208
198
|
| 402 | `quota_exceeded` | The plan's transcriptions-at-once limit is reached. `details`: `metric` (`concurrent_transcriptions`), `limit`, `used`, `plan`, `topUpUrl`. |
|
|
209
199
|
| 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
200
|
| 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`. |
|
|
@@ -8,7 +8,7 @@ from typing import Any, Callable, Dict, Mapping, Optional, Union
|
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
|
|
11
|
-
from .errors import from_envelope
|
|
11
|
+
from .errors import from_envelope, from_oauth
|
|
12
12
|
|
|
13
13
|
DEFAULT_BASE_URL = "https://api.vocalstack.com"
|
|
14
14
|
|
|
@@ -103,6 +103,8 @@ class Transport:
|
|
|
103
103
|
headers: Optional[Mapping[str, str]] = None,
|
|
104
104
|
idempotency_key: Optional[str] = None,
|
|
105
105
|
anonymous: bool = False,
|
|
106
|
+
no_retry: bool = False,
|
|
107
|
+
oauth_errors: bool = False,
|
|
106
108
|
) -> httpx.Response:
|
|
107
109
|
h: Dict[str, str] = {"accept": "application/json"}
|
|
108
110
|
if headers:
|
|
@@ -135,8 +137,8 @@ class Transport:
|
|
|
135
137
|
body = resp.json()
|
|
136
138
|
except (json.JSONDecodeError, ValueError):
|
|
137
139
|
pass
|
|
138
|
-
err = from_envelope(body, resp.status_code, resp.headers)
|
|
139
|
-
if retries(resp.status_code, err.code, method, idempotency_key) and attempt < self.max_retries:
|
|
140
|
+
err = (from_oauth if oauth_errors else from_envelope)(body, resp.status_code, resp.headers)
|
|
141
|
+
if not no_retry and retries(resp.status_code, err.code, method, idempotency_key) and attempt < self.max_retries:
|
|
140
142
|
wait = err.retry_after
|
|
141
143
|
if wait is None and err.code == "unavailable":
|
|
142
144
|
wait = UNAVAILABLE_WAIT_S
|
|
@@ -14,7 +14,7 @@ class _Base(BaseModel):
|
|
|
14
14
|
|
|
15
15
|
Access = Literal["public", "private"]
|
|
16
16
|
|
|
17
|
-
ErrorCode = Literal["invalid_audio", "invalid_options", "invalid_token", "capacity_exceeded", "slow_consumer", "engine_unavailable", "internal", "session_expired", "forbidden", "broadcast_archived", "not_found", "conflict", "invalid_state", "password_required", "rate_limited", "preview_pending", "language_unavailable", "suspended", "key_expired", "key_revoked", "key_from_browser", "insufficient_credit", "payment_method_required", "payment_method_in_use", "billing_details_required", "promo_code_invalid", "auto_top_up_off", "range_too_long", "quota_exceeded", "idempotency_key_reused", "method_not_allowed", "payload_too_large", "unsupported_media_type", "invalid_body", "timeout", "unavailable"]
|
|
17
|
+
ErrorCode = Literal["invalid_audio", "invalid_options", "invalid_token", "capacity_exceeded", "slow_consumer", "engine_unavailable", "internal", "session_expired", "forbidden", "broadcast_archived", "not_found", "conflict", "invalid_state", "password_required", "rate_limited", "preview_pending", "language_unavailable", "suspended", "key_expired", "key_revoked", "key_from_browser", "insufficient_credit", "usage_cap_reached", "payment_method_required", "terms_acceptance_required", "payment_method_in_use", "billing_details_required", "promo_code_invalid", "auto_top_up_off", "range_too_long", "quota_exceeded", "idempotency_key_reused", "method_not_allowed", "payload_too_large", "unsupported_media_type", "invalid_body", "timeout", "unavailable"]
|
|
18
18
|
|
|
19
19
|
AudioEncoding = Literal["pcm_f32le"]
|
|
20
20
|
|
|
@@ -46,7 +46,9 @@ IngestState = Literal["connecting", "streaming", "reconnecting", "stopped", "fai
|
|
|
46
46
|
|
|
47
47
|
InputKind = Literal["live", "file", "url", "stream"]
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
QueueReason = Literal["concurrency"]
|
|
50
|
+
|
|
51
|
+
JobState = Literal["queued", "fetching", "transcribing", "done", "failed", "cropping", "stopped"]
|
|
50
52
|
|
|
51
53
|
LinkKind = Literal["live", "file", "unknown"]
|
|
52
54
|
|
|
@@ -63,6 +65,11 @@ SentenceStatus = Literal["open", "closed", "dropped"]
|
|
|
63
65
|
VoiceStatusState = Literal["preparing", "unavailable"]
|
|
64
66
|
|
|
65
67
|
|
|
68
|
+
class AccountClosing(_Base):
|
|
69
|
+
at: str
|
|
70
|
+
unspent_paid_credit_refunded: bool = Field(alias="unspentPaidCreditRefunded")
|
|
71
|
+
|
|
72
|
+
|
|
66
73
|
class Actor(_Base):
|
|
67
74
|
api_key_id: Optional[str] = Field(default=None, alias="apiKeyId")
|
|
68
75
|
display_name: Optional[str] = Field(default=None, alias="displayName")
|
|
@@ -77,7 +84,6 @@ class WordAlt(_Base):
|
|
|
77
84
|
class Word(_Base):
|
|
78
85
|
alts: Optional[List["WordAlt"]] = None
|
|
79
86
|
end: float
|
|
80
|
-
llm: Optional[bool] = None
|
|
81
87
|
prob: Optional[float] = None
|
|
82
88
|
recognized: Optional[str] = None
|
|
83
89
|
script: Optional[bool] = None
|
|
@@ -166,6 +172,7 @@ class MoneyRange(_Base):
|
|
|
166
172
|
class AutoTopUpChoices(_Base):
|
|
167
173
|
amount_range: "MoneyRange" = Field(alias="amountRange")
|
|
168
174
|
amounts: List[float]
|
|
175
|
+
auto_topup_max_per_day: int = Field(alias="autoTopupMaxPerDay")
|
|
169
176
|
threshold_range: "MoneyRange" = Field(alias="thresholdRange")
|
|
170
177
|
thresholds: List[float]
|
|
171
178
|
|
|
@@ -178,8 +185,23 @@ class AutoTopUpSettings(_Base):
|
|
|
178
185
|
threshold: Optional[float] = None
|
|
179
186
|
|
|
180
187
|
|
|
188
|
+
class SettingLowering(_Base):
|
|
189
|
+
"""A lowering of a billing setting that changes the account's terms (the
|
|
190
|
+
days data is kept after a downgrade, the auto top-up daily limit),
|
|
191
|
+
told with at least 30 days' notice (docs/billing-price-changes.md §6)."""
|
|
192
|
+
|
|
193
|
+
after_date: Optional[str] = Field(default=None, alias="afterDate")
|
|
194
|
+
applies_at: str = Field(alias="appliesAt")
|
|
195
|
+
refund_deadline: str = Field(alias="refundDeadline")
|
|
196
|
+
refund_window_open: bool = Field(alias="refundWindowOpen")
|
|
197
|
+
renewal_on: Optional[str] = Field(default=None, alias="renewalOn")
|
|
198
|
+
to: int
|
|
199
|
+
|
|
200
|
+
|
|
181
201
|
class AutoTopUpView(_Base):
|
|
182
202
|
amount: Optional[float] = None
|
|
203
|
+
auto_topup_max_per_day: int = Field(alias="autoTopupMaxPerDay")
|
|
204
|
+
auto_topup_max_per_day_lowering: Optional["SettingLowering"] = Field(default=None, alias="autoTopupMaxPerDayLowering")
|
|
183
205
|
available: bool
|
|
184
206
|
enabled: bool
|
|
185
207
|
last: Optional["AutoTopUpAttempt"] = None
|
|
@@ -207,6 +229,13 @@ class CardView(_Base):
|
|
|
207
229
|
last4: str
|
|
208
230
|
|
|
209
231
|
|
|
232
|
+
class DowngradeKeepDays(_Base):
|
|
233
|
+
"""The account's days data is kept after a downgrade (Terms §14)."""
|
|
234
|
+
|
|
235
|
+
days: int
|
|
236
|
+
lowering: Optional["SettingLowering"] = None
|
|
237
|
+
|
|
238
|
+
|
|
210
239
|
class Limits2(_Base):
|
|
211
240
|
"""The limits of a version. `null` is unlimited."""
|
|
212
241
|
|
|
@@ -244,16 +273,32 @@ class MonthlyCreditView(_Base):
|
|
|
244
273
|
used: float
|
|
245
274
|
|
|
246
275
|
|
|
276
|
+
class PriceChange(_Base):
|
|
277
|
+
from_: float = Field(alias="from")
|
|
278
|
+
interval: str
|
|
279
|
+
to: float
|
|
280
|
+
|
|
281
|
+
|
|
247
282
|
class Rates(_Base):
|
|
248
|
-
"""The
|
|
283
|
+
"""The rate card of a version: dollars per hour, and the least a
|
|
284
|
+
transcription is billed."""
|
|
249
285
|
|
|
250
286
|
file_per_hour: float = Field(alias="filePerHour")
|
|
251
287
|
live_per_hour: float = Field(alias="livePerHour")
|
|
288
|
+
minimum_seconds: int = Field(alias="minimumSeconds")
|
|
252
289
|
|
|
253
290
|
|
|
254
291
|
class PendingChange(_Base):
|
|
292
|
+
after_date: Optional[str] = Field(default=None, alias="afterDate")
|
|
255
293
|
applies_at: str = Field(alias="appliesAt")
|
|
294
|
+
below_zero_limit_changes: bool = Field(alias="belowZeroLimitChanges")
|
|
295
|
+
cancel_before: Optional[str] = Field(default=None, alias="cancelBefore")
|
|
296
|
+
changes: str
|
|
297
|
+
price: Optional["PriceChange"] = None
|
|
256
298
|
rates: "Rates"
|
|
299
|
+
refund_deadline: Optional[str] = Field(default=None, alias="refundDeadline")
|
|
300
|
+
refund_window_open: bool = Field(alias="refundWindowOpen")
|
|
301
|
+
renewal_on: Optional[str] = Field(default=None, alias="renewalOn")
|
|
257
302
|
|
|
258
303
|
|
|
259
304
|
class PendingPayment(_Base):
|
|
@@ -278,37 +323,61 @@ class PlanRef(_Base):
|
|
|
278
323
|
class NextPayment(_Base):
|
|
279
324
|
amount: float
|
|
280
325
|
at: str
|
|
326
|
+
cancel_before: str = Field(alias="cancelBefore")
|
|
281
327
|
|
|
282
328
|
|
|
283
329
|
class PendingInterval(_Base):
|
|
284
|
-
applies_at: str = Field(alias="appliesAt")
|
|
330
|
+
applies_at: Optional[str] = Field(default=None, alias="appliesAt")
|
|
331
|
+
cancel_before: Optional[str] = Field(default=None, alias="cancelBefore")
|
|
285
332
|
interval: str
|
|
286
333
|
|
|
287
334
|
|
|
335
|
+
class ScheduledEnd(_Base):
|
|
336
|
+
at: str
|
|
337
|
+
reason: str
|
|
338
|
+
return_plan: Optional[str] = Field(default=None, alias="returnPlan")
|
|
339
|
+
unused_period_refunded: bool = Field(alias="unusedPeriodRefunded")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class SwitchPrices(_Base):
|
|
343
|
+
month: Optional[float] = None
|
|
344
|
+
year: Optional[float] = None
|
|
345
|
+
|
|
346
|
+
|
|
288
347
|
class SubscriptionView(_Base):
|
|
289
348
|
cancel_at_period_end: bool = Field(alias="cancelAtPeriodEnd")
|
|
349
|
+
cancel_reason: Optional[str] = Field(default=None, alias="cancelReason")
|
|
290
350
|
current_period_end: Optional[str] = Field(default=None, alias="currentPeriodEnd")
|
|
291
351
|
current_period_start: Optional[str] = Field(default=None, alias="currentPeriodStart")
|
|
292
352
|
interval: str
|
|
293
353
|
next_payment: Optional["NextPayment"] = Field(default=None, alias="nextPayment")
|
|
294
354
|
past_due_since: Optional[str] = Field(default=None, alias="pastDueSince")
|
|
295
355
|
pending_interval: Optional["PendingInterval"] = Field(default=None, alias="pendingInterval")
|
|
356
|
+
return_plan: Optional[str] = Field(default=None, alias="returnPlan")
|
|
357
|
+
scheduled_end: Optional["ScheduledEnd"] = Field(default=None, alias="scheduledEnd")
|
|
296
358
|
status: str
|
|
359
|
+
switch_prices: "SwitchPrices" = Field(alias="switchPrices")
|
|
360
|
+
yearly_opt_in: bool = Field(alias="yearlyOptIn")
|
|
297
361
|
|
|
298
362
|
|
|
299
363
|
class BillingView(_Base):
|
|
300
364
|
"""`GET /v1/billing`: the account as the Dashboard shows it. The plan's
|
|
301
|
-
|
|
365
|
+
below-zero limit (its amount) is never on it: `inBelowZero` says whether
|
|
302
366
|
running transcriptions go on below zero, never how far."""
|
|
303
367
|
|
|
368
|
+
account_closing: Optional["AccountClosing"] = Field(default=None, alias="accountClosing")
|
|
304
369
|
account_created: Optional[str] = Field(default=None, alias="accountCreated")
|
|
305
370
|
auto_top_up: "AutoTopUpView" = Field(alias="autoTopUp")
|
|
306
371
|
balance: float
|
|
372
|
+
below_zero_limit_changes_at: Optional[str] = Field(default=None, alias="belowZeroLimitChangesAt")
|
|
373
|
+
below_zero_limit_on: bool = Field(alias="belowZeroLimitOn")
|
|
307
374
|
can_start: bool = Field(alias="canStart")
|
|
375
|
+
can_start_live: bool = Field(alias="canStartLive")
|
|
308
376
|
card: Optional["CardView"] = None
|
|
377
|
+
downgrade_keep_days: "DowngradeKeepDays" = Field(alias="downgradeKeepDays")
|
|
309
378
|
enabled: bool
|
|
310
379
|
has_paid: bool = Field(alias="hasPaid")
|
|
311
|
-
|
|
380
|
+
in_below_zero: bool = Field(alias="inBelowZero")
|
|
312
381
|
limits: Optional["Limits2"] = None
|
|
313
382
|
limits_usage: Optional["LimitsUsage"] = Field(default=None, alias="limitsUsage")
|
|
314
383
|
low_balance_alert: "LowBalanceAlertView" = Field(alias="lowBalanceAlert")
|
|
@@ -321,6 +390,7 @@ running transcriptions go on below zero, never how far."""
|
|
|
321
390
|
rates: Optional["Rates"] = None
|
|
322
391
|
retention_deadline: Optional[str] = Field(default=None, alias="retentionDeadline")
|
|
323
392
|
start_block: Optional[str] = Field(default=None, alias="startBlock")
|
|
393
|
+
start_block_live: Optional[str] = Field(default=None, alias="startBlockLive")
|
|
324
394
|
subscription: Optional["SubscriptionView"] = None
|
|
325
395
|
top_up_url: str = Field(alias="topUpUrl")
|
|
326
396
|
|
|
@@ -363,7 +433,6 @@ design's settings sheet): what a recording made in it starts with."""
|
|
|
363
433
|
|
|
364
434
|
confidence: Optional["ConfidencePolicy"] = None
|
|
365
435
|
latency_mode: Optional[LatencyMode] = Field(default=None, alias="latencyMode")
|
|
366
|
-
llm_fix: Optional[bool] = Field(default=None, alias="llmFix")
|
|
367
436
|
max_duration_s: Optional[int] = Field(default=None, alias="maxDurationS")
|
|
368
437
|
max_pause_s: Optional[int] = Field(default=None, alias="maxPauseS")
|
|
369
438
|
silence_auto_pause_s: Optional[int] = Field(default=None, alias="silenceAutoPauseS")
|
|
@@ -452,7 +521,10 @@ class RecordingCharge(_Base):
|
|
|
452
521
|
"""A charge on the wire (`GET /v1/billing/charges`)."""
|
|
453
522
|
|
|
454
523
|
amount: float
|
|
524
|
+
audio_seconds: float = Field(alias="audioSeconds")
|
|
525
|
+
covered_seconds: float = Field(alias="coveredSeconds")
|
|
455
526
|
kind: str
|
|
527
|
+
minimum_applied: bool = Field(alias="minimumApplied")
|
|
456
528
|
name: Optional[str] = None
|
|
457
529
|
opened_at: str = Field(alias="openedAt")
|
|
458
530
|
rate_per_hour: float = Field(alias="ratePerHour")
|
|
@@ -583,6 +655,7 @@ class CreateCheckout(_Base):
|
|
|
583
655
|
kind: str
|
|
584
656
|
plan_id: Optional[str] = Field(default=None, alias="planId")
|
|
585
657
|
promo_code: Optional[str] = Field(default=None, alias="promoCode")
|
|
658
|
+
# Deprecated and ignored: `acceptTerms` is the one consent.
|
|
586
659
|
waive_withdrawal: Optional[bool] = Field(default=None, alias="waiveWithdrawal")
|
|
587
660
|
|
|
588
661
|
|
|
@@ -616,7 +689,6 @@ class SettingsPatch(_Base):
|
|
|
616
689
|
|
|
617
690
|
confidence: Optional["ConfidencePolicy"] = None
|
|
618
691
|
latency_mode: Optional[LatencyMode] = Field(default=None, alias="latencyMode")
|
|
619
|
-
llm_fix: Optional[bool] = Field(default=None, alias="llmFix")
|
|
620
692
|
max_duration_s: Optional[int] = Field(default=None, alias="maxDurationS")
|
|
621
693
|
max_pause_s: Optional[int] = Field(default=None, alias="maxPauseS")
|
|
622
694
|
silence_auto_pause_s: Optional[int] = Field(default=None, alias="silenceAutoPauseS")
|
|
@@ -715,6 +787,13 @@ class JobProgress(_Base):
|
|
|
715
787
|
total_s: Optional[float] = Field(default=None, alias="totalS")
|
|
716
788
|
|
|
717
789
|
|
|
790
|
+
class JobQueue(_Base):
|
|
791
|
+
"""Why a `queued` job is waiting, and where it stands (`Job.queue`)."""
|
|
792
|
+
|
|
793
|
+
position: int
|
|
794
|
+
reason: QueueReason
|
|
795
|
+
|
|
796
|
+
|
|
718
797
|
class KnownVoice(_Base):
|
|
719
798
|
"""One voice a realtime session may recognise a speaker as.
|
|
720
799
|
|
|
@@ -773,8 +852,16 @@ class LedgerPage(_Base):
|
|
|
773
852
|
next_cursor: Optional[str] = Field(default=None, alias="nextCursor")
|
|
774
853
|
|
|
775
854
|
|
|
855
|
+
class PageMedia(_Base):
|
|
856
|
+
"""A recording found on a page."""
|
|
857
|
+
|
|
858
|
+
title: Optional[str] = None
|
|
859
|
+
url: str
|
|
860
|
+
|
|
861
|
+
|
|
776
862
|
class LinkProbe(_Base):
|
|
777
863
|
kind: LinkKind
|
|
864
|
+
media: Optional[List["PageMedia"]] = None
|
|
778
865
|
ok: bool
|
|
779
866
|
problem: Optional[str] = None
|
|
780
867
|
source: str
|
|
@@ -820,11 +907,13 @@ class Payment(_Base):
|
|
|
820
907
|
|
|
821
908
|
amount: float
|
|
822
909
|
at: str
|
|
910
|
+
business_purchase: bool = Field(alias="businessPurchase")
|
|
823
911
|
description: str
|
|
824
912
|
document: Optional[str] = None
|
|
825
913
|
id: str
|
|
826
914
|
kind: str
|
|
827
915
|
status: str
|
|
916
|
+
tax_id: Optional[str] = Field(default=None, alias="taxId")
|
|
828
917
|
|
|
829
918
|
|
|
830
919
|
class PaymentDocument(_Base):
|
|
@@ -865,9 +954,9 @@ class PlanListing(_Base):
|
|
|
865
954
|
"""One plan on the price list: its current version's rates, prices,
|
|
866
955
|
credit, limits and features."""
|
|
867
956
|
|
|
957
|
+
below_zero_limit_on: bool = Field(alias="belowZeroLimitOn")
|
|
868
958
|
billed_externally: bool = Field(alias="billedExternally")
|
|
869
959
|
features: Any
|
|
870
|
-
grace_on_zero: bool = Field(alias="graceOnZero")
|
|
871
960
|
id: str
|
|
872
961
|
kind: str
|
|
873
962
|
limits: "Limits2"
|
|
@@ -883,7 +972,7 @@ class PlanList(_Base):
|
|
|
883
972
|
"""`GET /v1/billing/plans`."""
|
|
884
973
|
|
|
885
974
|
auto_top_up: "AutoTopUpChoices" = Field(alias="autoTopUp")
|
|
886
|
-
|
|
975
|
+
downgrade_keep_days: int = Field(alias="downgradeKeepDays")
|
|
887
976
|
enabled: bool
|
|
888
977
|
low_balance_alert: "LowBalanceAlertChoices" = Field(alias="lowBalanceAlert")
|
|
889
978
|
plans: List["PlanListing"]
|
|
@@ -1140,7 +1229,6 @@ class SessionOptions(_Base):
|
|
|
1140
1229
|
identify_voices: Optional[List["KnownVoice"]] = Field(default=None, alias="identifyVoices")
|
|
1141
1230
|
input: Optional["InputOptions"] = None
|
|
1142
1231
|
latency_mode: Optional[LatencyMode] = Field(default=None, alias="latencyMode")
|
|
1143
|
-
llm_fix: Optional[bool] = Field(default=None, alias="llmFix")
|
|
1144
1232
|
sentences: Optional["SentenceOptions"] = None
|
|
1145
1233
|
speaker_labels: Optional[SpeakerLabels] = Field(default=None, alias="speakerLabels")
|
|
1146
1234
|
spoken_language: Optional[str] = Field(default=None, alias="spokenLanguage")
|
|
@@ -1153,7 +1241,10 @@ class SessionOptions(_Base):
|
|
|
1153
1241
|
class SetInterval(_Base):
|
|
1154
1242
|
"""`POST /v1/billing/subscription/interval`."""
|
|
1155
1243
|
|
|
1244
|
+
accept_terms: Optional[bool] = Field(default=None, alias="acceptTerms")
|
|
1245
|
+
expected_price: Optional[float] = Field(default=None, alias="expectedPrice")
|
|
1156
1246
|
interval: str
|
|
1247
|
+
no_new_withdrawal_period_shown: Optional[bool] = Field(default=None, alias="noNewWithdrawalPeriodShown")
|
|
1157
1248
|
|
|
1158
1249
|
|
|
1159
1250
|
class ShareLink(_Base):
|
|
@@ -1241,6 +1332,7 @@ class TranscriptionJob(_Base):
|
|
|
1241
1332
|
peaks: Optional[List[float]] = None
|
|
1242
1333
|
preview: Optional[bool] = None
|
|
1243
1334
|
progress: "JobProgress"
|
|
1335
|
+
queue: Optional["JobQueue"] = None
|
|
1244
1336
|
recording_id: str = Field(alias="recordingId")
|
|
1245
1337
|
source: Optional["SourceInfo"] = None
|
|
1246
1338
|
started_at: Optional[str] = Field(default=None, alias="startedAt")
|
|
@@ -1314,6 +1406,36 @@ class UploadRecording(_Base):
|
|
|
1314
1406
|
url: Optional[str] = None
|
|
1315
1407
|
|
|
1316
1408
|
|
|
1409
|
+
class UsageCharge(_Base):
|
|
1410
|
+
"""One charge as the usage report counts it (`GET /v1/billing/usage/charges`):
|
|
1411
|
+
a settled `charge` ledger entry, a `charge_correction` (money given
|
|
1412
|
+
back: `correction`, a negative amount), or a charge still open at its
|
|
1413
|
+
current amount."""
|
|
1414
|
+
|
|
1415
|
+
amount: float
|
|
1416
|
+
correction: bool
|
|
1417
|
+
date: str
|
|
1418
|
+
kind: str
|
|
1419
|
+
name: Optional[str] = None
|
|
1420
|
+
opened_at: str = Field(alias="openedAt")
|
|
1421
|
+
rate_per_hour: float = Field(alias="ratePerHour")
|
|
1422
|
+
realtime_session_id: Optional[str] = Field(default=None, alias="realtimeSessionId")
|
|
1423
|
+
recording_id: Optional[str] = Field(default=None, alias="recordingId")
|
|
1424
|
+
seconds: float
|
|
1425
|
+
source: str
|
|
1426
|
+
state: str
|
|
1427
|
+
transcription_id: Optional[str] = Field(default=None, alias="transcriptionId")
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
class UsageChargePage(_Base):
|
|
1431
|
+
"""`GET /v1/billing/usage/charges`."""
|
|
1432
|
+
|
|
1433
|
+
charges: List["UsageCharge"]
|
|
1434
|
+
count: int
|
|
1435
|
+
next_cursor: Optional[str] = Field(default=None, alias="nextCursor")
|
|
1436
|
+
total: float
|
|
1437
|
+
|
|
1438
|
+
|
|
1317
1439
|
class UsageRow(_Base):
|
|
1318
1440
|
"""One day, source and kind."""
|
|
1319
1441
|
|
|
@@ -1396,6 +1518,14 @@ class VoiceTrack(_Base):
|
|
|
1396
1518
|
word_times: Optional[Dict[str, List[List[float]]]] = Field(default=None, alias="wordTimes")
|
|
1397
1519
|
|
|
1398
1520
|
|
|
1521
|
+
class YearlyOptIn(_Base):
|
|
1522
|
+
"""`POST /v1/billing/subscription/yearly-opt-in`."""
|
|
1523
|
+
|
|
1524
|
+
accept_terms: Optional[bool] = Field(default=None, alias="acceptTerms")
|
|
1525
|
+
expected_price: Optional[float] = Field(default=None, alias="expectedPrice")
|
|
1526
|
+
no_new_withdrawal_period_shown: Optional[bool] = Field(default=None, alias="noNewWithdrawalPeriodShown")
|
|
1527
|
+
|
|
1528
|
+
|
|
1399
1529
|
class CaptureSourceMic(_Base):
|
|
1400
1530
|
kind: Literal["mic"] = "mic"
|
|
1401
1531
|
|
|
@@ -1417,7 +1547,6 @@ class Cancel(_Base):
|
|
|
1417
1547
|
|
|
1418
1548
|
class SessionUpdate(_Base):
|
|
1419
1549
|
latency_mode: Optional[LatencyMode] = Field(default=None, alias="latencyMode")
|
|
1420
|
-
llm_fix: Optional[bool] = Field(default=None, alias="llmFix")
|
|
1421
1550
|
speaker_labels: Optional[SpeakerLabels] = Field(default=None, alias="speakerLabels")
|
|
1422
1551
|
spoken_language: Optional[str] = Field(default=None, alias="spokenLanguage")
|
|
1423
1552
|
translate: Optional[List[str]] = None
|
|
@@ -30,6 +30,7 @@ from .realtime import Realtime
|
|
|
30
30
|
from .resources import (
|
|
31
31
|
Billing,
|
|
32
32
|
Broadcasts,
|
|
33
|
+
Connect,
|
|
33
34
|
Corrections,
|
|
34
35
|
Events,
|
|
35
36
|
Jobs,
|
|
@@ -88,6 +89,7 @@ class VocalStack:
|
|
|
88
89
|
self.voices = Voices(t)
|
|
89
90
|
self.search = Search(t)
|
|
90
91
|
self.me = Me(t)
|
|
92
|
+
self.connect = Connect(t)
|
|
91
93
|
self.billing = Billing(t)
|
|
92
94
|
self.languages = Languages(t)
|
|
93
95
|
self.rtmp = Rtmp(t)
|
|
@@ -81,3 +81,17 @@ def from_envelope(
|
|
|
81
81
|
return VocalStackError(
|
|
82
82
|
"internal", f"unexpected error (status {status})", status=status, headers=headers
|
|
83
83
|
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def from_oauth(
|
|
87
|
+
body: Any, status: Optional[int] = None, headers: Optional[Mapping[str, str]] = None
|
|
88
|
+
) -> VocalStackError:
|
|
89
|
+
"""A token endpoint's ``400 { error, error_description }`` (RFC 6749
|
|
90
|
+
§5.2) as a :class:`VocalStackError` whose ``code`` is the ``error``
|
|
91
|
+
(``invalid_request``, ``invalid_grant``, ``unsupported_grant_type``);
|
|
92
|
+
any other answer goes through the envelope."""
|
|
93
|
+
if status == 400 and isinstance(body, dict) and isinstance(body.get("error"), str):
|
|
94
|
+
return VocalStackError(
|
|
95
|
+
body["error"], body.get("error_description") or body["error"], status=status, headers=headers
|
|
96
|
+
)
|
|
97
|
+
return from_envelope(body, status, headers)
|
|
@@ -373,11 +373,11 @@ class Recordings(_Group):
|
|
|
373
373
|
return _one(m.SummaryStatus, data)
|
|
374
374
|
|
|
375
375
|
async def export(
|
|
376
|
-
self, id: str, format: str, *, lang: Optional[str] = None, share_code: Optional[str] = None
|
|
376
|
+
self, id: str, format: str, *, lang: Optional[str] = None, share_code: Optional[str] = None, listener_token: Optional[str] = None
|
|
377
377
|
) -> bytes:
|
|
378
|
-
"""An export file: ``json``, ``srt``, ``xlsx`` or ``pdf``; ``lang`` makes it read in that language."""
|
|
378
|
+
"""An export file: ``json``, ``srt``, ``xlsx`` or ``pdf``; ``lang`` makes it read in that language. ``listener_token``: a broadcast listener's, once the recording has ended."""
|
|
379
379
|
resp = await self._t.raw(
|
|
380
|
-
"GET", self._path(id, "/export"), headers={"accept": "*/*"}, query={"format": format, "lang": lang, "shareCode": share_code}
|
|
380
|
+
"GET", self._path(id, "/export"), headers={"accept": "*/*"}, query={"format": format, "lang": lang, "shareCode": share_code, "listenerToken": listener_token}
|
|
381
381
|
)
|
|
382
382
|
return resp.content
|
|
383
383
|
|
|
@@ -472,7 +472,7 @@ class Recordings(_Group):
|
|
|
472
472
|
return await websockets.connect(url, max_size=None, additional_headers=headers)
|
|
473
473
|
|
|
474
474
|
|
|
475
|
-
_TERMINAL = {"done", "failed", "cropping"}
|
|
475
|
+
_TERMINAL = {"done", "failed", "stopped", "cropping"}
|
|
476
476
|
|
|
477
477
|
|
|
478
478
|
class Jobs(_Group):
|
|
@@ -701,6 +701,31 @@ class Me(_Group):
|
|
|
701
701
|
return await self._t.json("GET", "/v1/me")
|
|
702
702
|
|
|
703
703
|
|
|
704
|
+
class Connect(_Group):
|
|
705
|
+
"""``vs.connect``: one-click Connect, on the app's server."""
|
|
706
|
+
|
|
707
|
+
async def token(self, *, code: str, code_verifier: str, redirect_uri: str) -> Dict[str, Any]:
|
|
708
|
+
"""Redeem the ``code`` the dashboard sent to the app's ``redirect_uri``
|
|
709
|
+
for an API key (OAuth 2.0 authorization code + PKCE): ``code_verifier``
|
|
710
|
+
is the verifier whose ``BASE64URL(SHA256(verifier))`` was the link's
|
|
711
|
+
``code_challenge``, ``redirect_uri`` exactly the link's. Returns the
|
|
712
|
+
wire's ``{ access_token, token_type, key_id, scope, account }``;
|
|
713
|
+
``access_token`` is the key, shown once. No credential is sent, even
|
|
714
|
+
when the client has one, and the call is never retried: the code
|
|
715
|
+
works once. A refusal raises :class:`VocalStackError` whose ``code``
|
|
716
|
+
is OAuth's ``error`` (``invalid_grant``: an unknown, spent or expired
|
|
717
|
+
code, or a verifier or ``redirect_uri`` that does not match)."""
|
|
718
|
+
form = {
|
|
719
|
+
"grant_type": "authorization_code",
|
|
720
|
+
"code": code,
|
|
721
|
+
"code_verifier": code_verifier,
|
|
722
|
+
"redirect_uri": redirect_uri,
|
|
723
|
+
}
|
|
724
|
+
return await self._t.json(
|
|
725
|
+
"POST", "/v1/connect/token", data=form, anonymous=True, no_retry=True, oauth_errors=True
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
|
|
704
729
|
class Billing(_Group):
|
|
705
730
|
"""``vs.billing``: the account's plan, credit balance and limits, the
|
|
706
731
|
price list, the ledger and the per-recording charges."""
|
|
@@ -723,9 +748,9 @@ class Billing(_Group):
|
|
|
723
748
|
|
|
724
749
|
async def create_checkout(self, *, idempotency_key: Optional[str] = None, **fields: Any) -> m.CheckoutView:
|
|
725
750
|
"""Open a checkout at the payment provider (``kind="topup", amount=``
|
|
726
|
-
or ``kind="subscribe", plan_id=, interval=``, with ``accept_terms=True
|
|
727
|
-
and
|
|
728
|
-
signed-in person only, never an API key."""
|
|
751
|
+
or ``kind="subscribe", plan_id=, interval=``, with ``accept_terms=True``:
|
|
752
|
+
the Terms and the request that the purchase start now); send the
|
|
753
|
+
customer to ``url``. A signed-in person only, never an API key."""
|
|
729
754
|
body = _body(m.CreateCheckout, fields)
|
|
730
755
|
return _one(m.CheckoutView, await self._t.json("POST", "/v1/billing/checkouts", json_body=body, idempotency_key=idempotency_key))
|
|
731
756
|
|
|
@@ -760,10 +785,19 @@ class Billing(_Group):
|
|
|
760
785
|
return _one(m.LowBalanceAlertView, await self._t.json("PUT", "/v1/billing/low-balance-alert", json_body=body))
|
|
761
786
|
|
|
762
787
|
async def set_interval(self, **fields: Any) -> m.SubscriptionView:
|
|
763
|
-
"""Renew ``interval="month"`` or ``"year"`` from the next period
|
|
788
|
+
"""Renew ``interval="month"`` or ``"year"`` from the next period, at
|
|
789
|
+
``subscription.switch_prices`` (needs ``accept_terms=True`` and
|
|
790
|
+
``no_new_withdrawal_period_shown=True``)."""
|
|
764
791
|
body = _body(m.SetInterval, fields)
|
|
765
792
|
return _one(m.SubscriptionView, await self._t.json("POST", "/v1/billing/subscription/interval", json_body=body))
|
|
766
793
|
|
|
794
|
+
async def opt_in_yearly(self, **fields: Any) -> m.SubscriptionView:
|
|
795
|
+
"""Keep the subscription by switching to yearly when monthly billing
|
|
796
|
+
is withdrawn (``subscription.yearly_opt_in``); needs
|
|
797
|
+
``accept_terms=True`` and ``no_new_withdrawal_period_shown=True``."""
|
|
798
|
+
body = _body(m.YearlyOptIn, fields)
|
|
799
|
+
return _one(m.SubscriptionView, await self._t.json("POST", "/v1/billing/subscription/yearly-opt-in", json_body=body))
|
|
800
|
+
|
|
767
801
|
async def unset_interval(self) -> m.SubscriptionView:
|
|
768
802
|
"""Take back a scheduled interval switch."""
|
|
769
803
|
return _one(m.SubscriptionView, await self._t.json("DELETE", "/v1/billing/subscription/interval"))
|
|
@@ -772,6 +806,21 @@ class Billing(_Group):
|
|
|
772
806
|
"""What the account spent per day (``YYYY-MM-DD`` in ``tz``), source and kind."""
|
|
773
807
|
return _one(m.UsageReport, await self._t.json("GET", "/v1/billing/usage", query={"startDate": start_date, "endDate": end_date, "tz": tz}))
|
|
774
808
|
|
|
809
|
+
async def usage_charges(
|
|
810
|
+
self,
|
|
811
|
+
*,
|
|
812
|
+
start_date: Optional[str] = None,
|
|
813
|
+
end_date: Optional[str] = None,
|
|
814
|
+
tz: Optional[str] = None,
|
|
815
|
+
kind: Optional[str] = None,
|
|
816
|
+
source: Optional[str] = None,
|
|
817
|
+
cursor: Optional[str] = None,
|
|
818
|
+
limit: Optional[int] = None,
|
|
819
|
+
) -> m.UsageChargePage:
|
|
820
|
+
"""The usage report's charges one by one, newest first; ``kind`` (``file``/``live``) and ``source`` (``dashboard`` or a key id) filter, ``count`` and ``total`` cover every match."""
|
|
821
|
+
query = {"startDate": start_date, "endDate": end_date, "tz": tz, "kind": kind, "source": source, "cursor": cursor, "limit": limit}
|
|
822
|
+
return _one(m.UsageChargePage, await self._t.json("GET", "/v1/billing/usage/charges", query=query))
|
|
823
|
+
|
|
775
824
|
async def payments(self, *, cursor: Optional[str] = None, limit: Optional[int] = None) -> m.PaymentPage:
|
|
776
825
|
"""The payments history, newest first."""
|
|
777
826
|
return _one(m.PaymentPage, await self._t.json("GET", "/v1/billing/payments", query={"cursor": cursor, "limit": limit}))
|
vocalstack-1.0.0a0/.gitignore
DELETED
|
File without changes
|