fcaai 0.4.0__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,43 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `fcaai` Python package. Prices are always USD.
4
+
5
+ ## 0.4.0 — 2026-10-30
6
+
7
+ Full parity with the public `/v1` REST API.
8
+
9
+ - **Multi-step templates:** `templates.run_package(slug, values, wait=…)`, `templates.get_run(run_id, wait=…)`, `templates.cancel_run(run_id)`,
10
+ `templates.wait_run(run_id)`. `templates.run()` now raises `FcaaiError` `template_run` (status 202, the run in `err.body`) when a
11
+ template turns out to be multi-step, instead of polling the wrong endpoint.
12
+ - `templates.run(slug, values, model=…, brand_kit_id=…, project=…)` — run on another allowed model (`Template.models`), apply a style kit,
13
+ file the output into an Architecture Studio project (response `arch_project`).
14
+ - `Template` gains `models`, `studio`, `subcategory`, `tags`, `package` and `is_package`; `TemplateInput` gains `role`, `unit`, `placeholder`.
15
+ - **Voices:** `voices.list()`, `voices.clone(files, name=…, consent=True, consent_version=…)` (multipart, never retried),
16
+ `voices.rename(id, name)`, `voices.delete(id)`; `models.voice_styles(locale=…, category=…)`.
17
+ - **Elements & style kits:** `elements.list(kind=…, q=…, limit=…, cursor=…)`, `style_kits.list(q=…)`.
18
+ - `generations.create(…, brand_kit_id=…, external_user_id=…)`.
19
+ - **Reseller:** `reseller.subtenants.list()`, `reseller.subtenants.create(name, …)` and `reseller.subtenants.create_key(id, name, …)`
20
+ (both never retried), `reseller.usage(from_=…, to=…)`.
21
+ - **Architecture Studio projects:** `architecture_projects.list/create/get/add_item` (same `/v1/architecture/projects` paths as the
22
+ TypeScript SDK `architectureProjects`); `add_compliance_label/list_compliance_labels/revoke_compliance_label` ("Representative image"
23
+ labels, free); `product_list` (billed by tokens, never retried) / `list_product_lists` / `get_product_list(format="csv")`.
24
+ - Docstrings for `generations.list` and `account.usage`; `models.get(…, mask=True)` documented.
25
+ - Packaging: MIT `LICENSE` file, `py.typed` marker (PEP 561), Python 3.9–3.13 classifiers, project URLs.
26
+
27
+ ## 0.3.0 — 2026-10-15
28
+
29
+ - Canvas surfaces, same `/v1` paths as the TypeScript SDK 0.3: `timelines.create/get/preview/approve/unapprove/final/cut/act/wait`,
30
+ `farm.jobs.list/get/pause/resume`, `community.list/get`, `approvals.list/get/decide`, `plans.read` (never retried — billed per read),
31
+ `panoramas.finish`, `video_edits.create` (returns a `Generation`), `flows.list/get/run/get_run/wait/cancel`.
32
+ - Per-request `max_retries` override in the transport.
33
+
34
+ ## 0.2.0 — 2026-09-28
35
+
36
+ - `audio.speech/sfx/music`, `model3d.from_image`, `images.create` (+ native `images.generate` with `resolution="4k"`, `count`),
37
+ `templates.list/get/run`, `models.voices`, `models.list(type=)` server-side.
38
+ - Typed `Output` / `Voice` / `Template` dataclasses; `Generation.outputs` with preview URLs. `wait()` unchanged.
39
+
40
+ ## 0.1.0
41
+
42
+ - First release: generations, uploads (simple + resumable chunked), account, webhooks (+ signature verification), chat completions,
43
+ OpenAI-compatible image generation.
fcaai-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FCA Studio LLC
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.
@@ -0,0 +1,6 @@
1
+ include README.md CHANGELOG.md LICENSE pyproject.toml
2
+ include fcaai/py.typed
3
+ recursive-include tests *.py
4
+ global-exclude *.py[cod] __pycache__ .pytest_cache .venv .env *.egg-info
5
+ prune .venv
6
+ prune .pytest_cache
fcaai-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: fcaai
3
+ Version: 0.4.0
4
+ Summary: Official Python client for the fcaai API — image, video, audio, 3D and chat generation behind one key.
5
+ Author: FCA Studio LLC
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://fcaai.ai
8
+ Project-URL: Documentation, https://fcaai.ai/docs
9
+ Project-URL: Source, https://fcaai.ai/developers#sdk
10
+ Project-URL: Changelog, https://fcaai.ai/developers#sdk
11
+ Keywords: fcaai,ai,api,sdk,image-generation,video-generation,text-to-speech,3d
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Multimedia :: Graphics
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Dynamic: license-file
30
+
31
+ # fcaai (Python)
32
+
33
+ Official Python client for the [fcaai API](https://api.fcaai.ai/docs). Standard library only (no `requests`/`httpx`), Python 3.9+. All prices in USD.
34
+
35
+ ```bash
36
+ pip install fcaai
37
+ export FCAAI_API_KEY=fcaai_live_…
38
+ ```
39
+
40
+ ```python
41
+ from fcaai import Fcaai
42
+
43
+ client = Fcaai() # reads FCAAI_API_KEY
44
+
45
+ print(client.models.get("google/nano-banana-pro", resolution="2k")["estimate"]["price_display"])
46
+
47
+ gen = client.generations.create("google/nano-banana-pro", {"prompt": "a paper plane on a wooden desk", "aspect_ratio": "16:9"})
48
+ gen.wait() # long-polls /v1/generations/{id}?wait=60
49
+ print(gen.status, gen.urls, gen.price_usd)
50
+
51
+ # Files: ≤ 50 MB in one request, bigger ones chunked (8 MB, 3 parallel, resumable with session_id=)
52
+ img = client.uploads.create("room.jpg", "image/jpeg")
53
+ video = client.uploads.create_chunked("walkthrough.mp4", mime="video/mp4", on_progress=lambda s, n: print(s * 100 // n, "%"))
54
+
55
+ clip = client.generations.create("fca/magic-v3", {"image": img["url"], "prompt": "slow dolly in", "duration": 5}, wait=600)
56
+ ```
57
+
58
+ ## Audio, 3D, 4K images and templates (0.2)
59
+
60
+ ```python
61
+ # Text to speech (MP3). Voices: [v.id for v in client.models.voices("minimax/speech-2.8")] → narrator, calm, …
62
+ clip = client.audio.speech("Welcome to the studio.", voice="narrator", wait=True)
63
+
64
+ # Sound effects: duration in seconds, seamless loop
65
+ rain = client.audio.sfx("rain on a tin roof", duration=6, loop=True, wait=True)
66
+
67
+ # One object photo → GLB (takes minutes)
68
+ chair = client.model3d.from_image(img["url"], detail="detailed", surface="textured", wait=1800)
69
+
70
+ # 4K on local models (2K render + detail upscaler), up to 4 outputs
71
+ shots = client.images.create("glass villa, blue hour", model="fca/magic-pro", resolution="4k", count=2, wait=True)
72
+ for o in shots.outputs: # Output dataclass: url, kind, mime, width, height, thumb_url, card_url, poster_url
73
+ print(o.url, o.thumb_url)
74
+
75
+ # Templates
76
+ tpl = client.templates.list()[0]
77
+ print([(i.key, i.type, i.required) for i in tpl.inputs])
78
+ run = client.templates.run(tpl.id, {"photo": img["url"]}, wait=True)
79
+ ```
80
+
81
+ ## Canvas: timelines, farm, community, approvals, plans, panoramas, video edits (0.3)
82
+
83
+ ```python
84
+ # Scene timeline: storyboard → cheap previews → approve → full quality → one MP4
85
+ tl = client.timelines.create([{"prompt": "Lina walks into the empty gallery at dawn", "duration": 5}],
86
+ {"model": "bytedance/seedance-2.0-fast", "imageModel": "fca/magic-pro", "aspect": "16:9"}, preview=True)
87
+ client.timelines.wait(tl["id"]); client.timelines.approve(tl["id"]); client.timelines.final(tl["id"])
88
+ client.timelines.wait(tl["id"], timeout=3600); client.timelines.cut(tl["id"])
89
+ print(client.timelines.wait(tl["id"])["cut"]["url"])
90
+
91
+ # Render farm (scenes sent from 3ds Max with the FCA plugin; FcaaiError not_enabled when the farm is off)
92
+ for j in client.farm.jobs.list(status="active"):
93
+ print(j["id"], j["status"], j["progress"])
94
+ client.farm.jobs.pause("fj_…") # the reserved amount stays on hold; paused time is not charged
95
+ client.farm.jobs.resume("fj_…")
96
+
97
+ page = client.community.list(type="flow", category="architecture", sort="popular")
98
+ res = client.approvals.list("<board-id>", status="pending")
99
+ client.approvals.decide("<board-id>", "<request-id>", "<option-id>", "changes", note="Warmer light, please.")
100
+ plan = client.plans.read(img["url"]) # billed per read, never retried
101
+ pano = client.panoramas.finish("https://…/pano.png") # free
102
+ edit = client.video_edits.create([{"url": "https://fcaai.ai/o/…/clip-1.mp4", "start": 0.5, "end": 4}], transition="fade", wait=True)
103
+ print(edit.urls[0])
104
+ ```
105
+
106
+ ## Voices, multi-step templates, elements, style kits, reseller (0.4)
107
+
108
+ ```python
109
+ # Clone your own voice (free; speech is billed as usual). Consent is required.
110
+ info = client.voices.list()
111
+ me = client.voices.clone(["take1.mp3", "take2.mp3"], name="My voice", consent=True,
112
+ consent_version=info["clone"]["consent_version"])
113
+ client.audio.speech("Hello from my own voice.", voice=me["id"], model="<speech-model-with-cloned-voices>", wait=True)
114
+ styles = client.models.voice_styles(locale="en") # ready-made speaking styles → styles["param"]
115
+
116
+ # Multi-step templates (Template.is_package) run as one template run
117
+ tpl = client.templates.get("<package-slug>")
118
+ run = client.templates.run_package(tpl.id, {"photo": img["url"]}, wait=True)
119
+ print(run["status"], run["price_usd"], [o["url"] for o in run["outputs"]])
120
+ # client.templates.get_run(run_id, wait=30) · client.templates.cancel_run(run_id)
121
+
122
+ # Single-step template on another allowed model, with a style kit, filed into an Architecture Studio project
123
+ kit = client.style_kits.list()[0]
124
+ g = client.templates.run("<slug>", {"photo": img["url"]}, model=tpl.models[-1]["id"], brand_kit_id=kit["id"], project="<project-id>")
125
+
126
+ # Element library (characters, products, places, materials) → references added server-side
127
+ lina = client.elements.list(kind="character", q="lina")["data"][0]
128
+ client.generations.create("fca/magic-pro", {"prompt": "@lina reading in a cafe", "element_ids": [lina["id"]]},
129
+ brand_kit_id=kit["id"], external_user_id="customer-42")
130
+
131
+ # Reseller keys (scope reseller:manage)
132
+ acme = client.reseller.subtenants.create("Acme Studio", retail_markup=1.5)
133
+ key = client.reseller.subtenants.create_key(acme["id"], "server", mode="live") # full key shown only here
134
+ print(client.reseller.usage(from_="2026-10-01", to="2026-10-31")["totals"])
135
+ ```
136
+
137
+ `images.generate(prompt, size=…, n=…)` stays OpenAI-compatible (returns a dict); passing `resolution`, `count`, `aspect_ratio`, `images` or `wait` makes it a native generation.
138
+
139
+ Also: `generations.get/list(status=, limit=)/cancel`, `account.get/balance/usage(from_=, to=)`, `uploads.create_session/get_session/put_chunk/complete_session/delete_session` (low-level resumable upload), `webhooks.list/create/delete/deliveries`, `chat.completions.create(messages)` (OpenAI schema, `fca/chat`), `images.generate(prompt, size=…)`.
140
+
141
+ Errors raise `fcaai.FcaaiError` (`status`, `type`, `code`, `message`, `request_id`, `doc_url`, `retry_after`, `body`). 429/5xx/network errors are retried (`max_retries=2`) honoring `Retry-After`. `create()` always sends an `Idempotency-Key`, so retries never double-charge.
142
+
143
+ ## Webhooks
144
+
145
+ ```python
146
+ from fcaai import verify_webhook_signature
147
+
148
+ ok = verify_webhook_signature(secret, request.headers["X-FCAAI-Signature"], request.get_data(), tolerance_sec=300)
149
+ ```
150
+
151
+ ## Changelog
152
+
153
+ Full history in `CHANGELOG.md` (included in the source distribution). **0.4.0** — full `/v1` parity: multi-step template runs, voice cloning + voice styles,
154
+ elements, style kits, reseller sub-tenants/keys/usage, `brand_kit_id` / `external_user_id` on generations, typed package (`py.typed`).
155
+
156
+ ## Tests
157
+
158
+ ```bash
159
+ python -m pytest -q # or: python -m unittest
160
+ ```
161
+
162
+ License: MIT © 2026 FCA Studio LLC.
fcaai-0.4.0/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # fcaai (Python)
2
+
3
+ Official Python client for the [fcaai API](https://api.fcaai.ai/docs). Standard library only (no `requests`/`httpx`), Python 3.9+. All prices in USD.
4
+
5
+ ```bash
6
+ pip install fcaai
7
+ export FCAAI_API_KEY=fcaai_live_…
8
+ ```
9
+
10
+ ```python
11
+ from fcaai import Fcaai
12
+
13
+ client = Fcaai() # reads FCAAI_API_KEY
14
+
15
+ print(client.models.get("google/nano-banana-pro", resolution="2k")["estimate"]["price_display"])
16
+
17
+ gen = client.generations.create("google/nano-banana-pro", {"prompt": "a paper plane on a wooden desk", "aspect_ratio": "16:9"})
18
+ gen.wait() # long-polls /v1/generations/{id}?wait=60
19
+ print(gen.status, gen.urls, gen.price_usd)
20
+
21
+ # Files: ≤ 50 MB in one request, bigger ones chunked (8 MB, 3 parallel, resumable with session_id=)
22
+ img = client.uploads.create("room.jpg", "image/jpeg")
23
+ video = client.uploads.create_chunked("walkthrough.mp4", mime="video/mp4", on_progress=lambda s, n: print(s * 100 // n, "%"))
24
+
25
+ clip = client.generations.create("fca/magic-v3", {"image": img["url"], "prompt": "slow dolly in", "duration": 5}, wait=600)
26
+ ```
27
+
28
+ ## Audio, 3D, 4K images and templates (0.2)
29
+
30
+ ```python
31
+ # Text to speech (MP3). Voices: [v.id for v in client.models.voices("minimax/speech-2.8")] → narrator, calm, …
32
+ clip = client.audio.speech("Welcome to the studio.", voice="narrator", wait=True)
33
+
34
+ # Sound effects: duration in seconds, seamless loop
35
+ rain = client.audio.sfx("rain on a tin roof", duration=6, loop=True, wait=True)
36
+
37
+ # One object photo → GLB (takes minutes)
38
+ chair = client.model3d.from_image(img["url"], detail="detailed", surface="textured", wait=1800)
39
+
40
+ # 4K on local models (2K render + detail upscaler), up to 4 outputs
41
+ shots = client.images.create("glass villa, blue hour", model="fca/magic-pro", resolution="4k", count=2, wait=True)
42
+ for o in shots.outputs: # Output dataclass: url, kind, mime, width, height, thumb_url, card_url, poster_url
43
+ print(o.url, o.thumb_url)
44
+
45
+ # Templates
46
+ tpl = client.templates.list()[0]
47
+ print([(i.key, i.type, i.required) for i in tpl.inputs])
48
+ run = client.templates.run(tpl.id, {"photo": img["url"]}, wait=True)
49
+ ```
50
+
51
+ ## Canvas: timelines, farm, community, approvals, plans, panoramas, video edits (0.3)
52
+
53
+ ```python
54
+ # Scene timeline: storyboard → cheap previews → approve → full quality → one MP4
55
+ tl = client.timelines.create([{"prompt": "Lina walks into the empty gallery at dawn", "duration": 5}],
56
+ {"model": "bytedance/seedance-2.0-fast", "imageModel": "fca/magic-pro", "aspect": "16:9"}, preview=True)
57
+ client.timelines.wait(tl["id"]); client.timelines.approve(tl["id"]); client.timelines.final(tl["id"])
58
+ client.timelines.wait(tl["id"], timeout=3600); client.timelines.cut(tl["id"])
59
+ print(client.timelines.wait(tl["id"])["cut"]["url"])
60
+
61
+ # Render farm (scenes sent from 3ds Max with the FCA plugin; FcaaiError not_enabled when the farm is off)
62
+ for j in client.farm.jobs.list(status="active"):
63
+ print(j["id"], j["status"], j["progress"])
64
+ client.farm.jobs.pause("fj_…") # the reserved amount stays on hold; paused time is not charged
65
+ client.farm.jobs.resume("fj_…")
66
+
67
+ page = client.community.list(type="flow", category="architecture", sort="popular")
68
+ res = client.approvals.list("<board-id>", status="pending")
69
+ client.approvals.decide("<board-id>", "<request-id>", "<option-id>", "changes", note="Warmer light, please.")
70
+ plan = client.plans.read(img["url"]) # billed per read, never retried
71
+ pano = client.panoramas.finish("https://…/pano.png") # free
72
+ edit = client.video_edits.create([{"url": "https://fcaai.ai/o/…/clip-1.mp4", "start": 0.5, "end": 4}], transition="fade", wait=True)
73
+ print(edit.urls[0])
74
+ ```
75
+
76
+ ## Voices, multi-step templates, elements, style kits, reseller (0.4)
77
+
78
+ ```python
79
+ # Clone your own voice (free; speech is billed as usual). Consent is required.
80
+ info = client.voices.list()
81
+ me = client.voices.clone(["take1.mp3", "take2.mp3"], name="My voice", consent=True,
82
+ consent_version=info["clone"]["consent_version"])
83
+ client.audio.speech("Hello from my own voice.", voice=me["id"], model="<speech-model-with-cloned-voices>", wait=True)
84
+ styles = client.models.voice_styles(locale="en") # ready-made speaking styles → styles["param"]
85
+
86
+ # Multi-step templates (Template.is_package) run as one template run
87
+ tpl = client.templates.get("<package-slug>")
88
+ run = client.templates.run_package(tpl.id, {"photo": img["url"]}, wait=True)
89
+ print(run["status"], run["price_usd"], [o["url"] for o in run["outputs"]])
90
+ # client.templates.get_run(run_id, wait=30) · client.templates.cancel_run(run_id)
91
+
92
+ # Single-step template on another allowed model, with a style kit, filed into an Architecture Studio project
93
+ kit = client.style_kits.list()[0]
94
+ g = client.templates.run("<slug>", {"photo": img["url"]}, model=tpl.models[-1]["id"], brand_kit_id=kit["id"], project="<project-id>")
95
+
96
+ # Element library (characters, products, places, materials) → references added server-side
97
+ lina = client.elements.list(kind="character", q="lina")["data"][0]
98
+ client.generations.create("fca/magic-pro", {"prompt": "@lina reading in a cafe", "element_ids": [lina["id"]]},
99
+ brand_kit_id=kit["id"], external_user_id="customer-42")
100
+
101
+ # Reseller keys (scope reseller:manage)
102
+ acme = client.reseller.subtenants.create("Acme Studio", retail_markup=1.5)
103
+ key = client.reseller.subtenants.create_key(acme["id"], "server", mode="live") # full key shown only here
104
+ print(client.reseller.usage(from_="2026-10-01", to="2026-10-31")["totals"])
105
+ ```
106
+
107
+ `images.generate(prompt, size=…, n=…)` stays OpenAI-compatible (returns a dict); passing `resolution`, `count`, `aspect_ratio`, `images` or `wait` makes it a native generation.
108
+
109
+ Also: `generations.get/list(status=, limit=)/cancel`, `account.get/balance/usage(from_=, to=)`, `uploads.create_session/get_session/put_chunk/complete_session/delete_session` (low-level resumable upload), `webhooks.list/create/delete/deliveries`, `chat.completions.create(messages)` (OpenAI schema, `fca/chat`), `images.generate(prompt, size=…)`.
110
+
111
+ Errors raise `fcaai.FcaaiError` (`status`, `type`, `code`, `message`, `request_id`, `doc_url`, `retry_after`, `body`). 429/5xx/network errors are retried (`max_retries=2`) honoring `Retry-After`. `create()` always sends an `Idempotency-Key`, so retries never double-charge.
112
+
113
+ ## Webhooks
114
+
115
+ ```python
116
+ from fcaai import verify_webhook_signature
117
+
118
+ ok = verify_webhook_signature(secret, request.headers["X-FCAAI-Signature"], request.get_data(), tolerance_sec=300)
119
+ ```
120
+
121
+ ## Changelog
122
+
123
+ Full history in `CHANGELOG.md` (included in the source distribution). **0.4.0** — full `/v1` parity: multi-step template runs, voice cloning + voice styles,
124
+ elements, style kits, reseller sub-tenants/keys/usage, `brand_kit_id` / `external_user_id` on generations, typed package (`py.typed`).
125
+
126
+ ## Tests
127
+
128
+ ```bash
129
+ python -m pytest -q # or: python -m unittest
130
+ ```
131
+
132
+ License: MIT © 2026 FCA Studio LLC.
@@ -0,0 +1,10 @@
1
+ """Official Python client for the fcaai API (https://api.fcaai.ai). Standard library only."""
2
+ from ._errors import FcaaiError, FcaaiTimeoutError
3
+ from .client import (CHUNK_SIZE, DEFAULT_BASE_URL, SIMPLE_UPLOAD_MAX, Fcaai, Generation, Output, Template, TemplateInput, Transport, Voice,
4
+ VoiceSample,
5
+ __version__, urllib_transport)
6
+ from .webhooks import construct_event, parse_signature_header, sign_webhook_payload, verify_webhook_signature
7
+
8
+ __all__ = ["Fcaai", "Generation", "Output", "Voice", "Template", "TemplateInput", "FcaaiError", "FcaaiTimeoutError", "verify_webhook_signature", "sign_webhook_payload",
9
+ "parse_signature_header", "construct_event", "Transport", "VoiceSample", "urllib_transport", "DEFAULT_BASE_URL",
10
+ "SIMPLE_UPLOAD_MAX", "CHUNK_SIZE", "__version__"]
@@ -0,0 +1,31 @@
1
+ from typing import Any, Optional
2
+
3
+
4
+ class FcaaiError(Exception):
5
+ """Raised for every non-2xx response (and network failures, status 0)."""
6
+
7
+ def __init__(self, message: str, *, status: int = 0, type: str = "api_error", code: str = "error",
8
+ request_id: Optional[str] = None, doc_url: Optional[str] = None,
9
+ retry_after: Optional[float] = None, body: Any = None) -> None:
10
+ super().__init__(message)
11
+ self.message = message
12
+ self.status = status
13
+ self.type = type
14
+ self.code = code
15
+ self.request_id = request_id
16
+ self.doc_url = doc_url
17
+ self.retry_after = retry_after
18
+ self.body = body
19
+
20
+ @property
21
+ def retryable(self) -> bool:
22
+ return self.status in (0, 408, 429) or self.status >= 500
23
+
24
+ def __repr__(self) -> str:
25
+ return f"FcaaiError(status={self.status}, code={self.code!r}, message={self.message!r}, request_id={self.request_id!r})"
26
+
27
+
28
+ class FcaaiTimeoutError(FcaaiError):
29
+ def __init__(self, message: str, generation_id: Optional[str] = None) -> None:
30
+ super().__init__(message, status=0, type="timeout", code="wait_timeout")
31
+ self.generation_id = generation_id