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.
- fcaai-0.4.0/CHANGELOG.md +43 -0
- fcaai-0.4.0/LICENSE +21 -0
- fcaai-0.4.0/MANIFEST.in +6 -0
- fcaai-0.4.0/PKG-INFO +162 -0
- fcaai-0.4.0/README.md +132 -0
- fcaai-0.4.0/fcaai/__init__.py +10 -0
- fcaai-0.4.0/fcaai/_errors.py +31 -0
- fcaai-0.4.0/fcaai/client.py +1120 -0
- fcaai-0.4.0/fcaai/py.typed +0 -0
- fcaai-0.4.0/fcaai/webhooks.py +59 -0
- fcaai-0.4.0/fcaai.egg-info/PKG-INFO +162 -0
- fcaai-0.4.0/fcaai.egg-info/SOURCES.txt +16 -0
- fcaai-0.4.0/fcaai.egg-info/dependency_links.txt +1 -0
- fcaai-0.4.0/fcaai.egg-info/top_level.txt +1 -0
- fcaai-0.4.0/pyproject.toml +44 -0
- fcaai-0.4.0/setup.cfg +4 -0
- fcaai-0.4.0/tests/__init__.py +0 -0
- fcaai-0.4.0/tests/test_client.py +598 -0
fcaai-0.4.0/CHANGELOG.md
ADDED
|
@@ -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.
|
fcaai-0.4.0/MANIFEST.in
ADDED
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
|