Unofficial async Python client for the Flowith.io web app
Project description
flowith
An unofficial async Python client for the Flowith.io web application.
Disclaimer: This library reverse-engineers the Flowith.io browser API. It is not affiliated with or endorsed by Flowith. Use responsibly.
Features
- Text generation — single-turn and multi-turn, streaming and non-streaming
- Image generation — Gemini, GPT Image, and more
- Video generation — Seedance, Kling, etc.
- File upload —
POST /file/storewith multipart support - Conversation management — create, list, rename, delete
- Flow canvas — read node graph for any conversation
- Credits & models — fetch balance, browse model catalog
- Templates — list and filter prompt templates
- Profile & subscriptions — read/update profile, list grants
- Supabase content — notices, projects, discussions, oracle stream
- Daily rewards — claim and check status
- Account auth — OAuth URL, auth user, refresh token
- Real-time SSE — listen on
/user_stream/streamfor live generation events - Async-first — built on
aiohttp, zero blocking calls
Installation
pip install flowith_webapi
Requires Python 3.10+.
Authentication
- Sign in at flowith.io with Google.
- After the OAuth redirect, copy the
access_tokenfrom the URL hash:https://flowith.io/#access_token=eyJhbGci...&... - Your user ID (
subclaim in the JWT) is also needed for SSE streaming — it is parsed automatically from the JWT if you do not supply it explicitly.
To enable token rotation, also capture the refresh token from Supabase.
Generate the OAuth URL with access_type=offline and prompt=consent so
Google issues one on the first consent screen:
url = client.get_oauth_url(
provider="google",
redirect_to="https://flowith.io",
access_type="offline",
prompt="consent",
)
print(url)
import os
TOKEN = os.environ["FLOWITH_TOKEN"]
USER_ID = os.environ["FLOWITH_USER_ID"] # optional — parsed from JWT
REFRESH = os.environ.get("FLOWITH_REFRESH_TOKEN", "")
Keep these secret — they grant full access to your Flowith account.
How it works (SSE architecture)
The Flowith web app maintains two persistent SSE connections:
| Endpoint | Purpose |
|---|---|
GET /user_stream/sse?user_id=… |
Keepalive — emits {"heartbeat": …} every ~5 s |
GET /user_stream/stream?user_id=… |
Data channel — emits text_delta, result, image/video URLs |
Both connections send the JWT as a bare token in the authorization header
(no Bearer prefix) — this matches what the edge server expects.
When generate() is called the client:
- Creates the Supabase conversation and user/AI node rows.
- POSTs the completion request to
/completion/async(returns immediately). - Listens on
/user_stream/streamfor events whosenodeIdmatches the AI node. - Falls back to polling
flow_nodeon Supabase if the stream closes early.
Quick start
import asyncio
from flowith_webapi import FlowithClient
async def main():
async with FlowithClient(TOKEN, user_id=USER_ID, refresh_token=REFRESH) as client:
r = await client.generate("What is the capital of Poland?")
print(r.text) # "Warsaw"
asyncio.run(main())
Usage
Single-turn generation
r = await client.generate("Explain quantum entanglement in one sentence.")
print(r.text)
print(r.model) # model ID used
print(r.conv_id) # conversation UUID
Streaming
async for chunk in client.generate_stream("Write me a haiku about async Python."):
print(chunk.text_delta, end="", flush=True)
print()
Multi-turn session
session = client.start_conversation(model="gemini-3.1-pro-preview")
r1 = await session.send("My name is Alice.")
r2 = await session.send("What is my name?")
print(r2.text) # "Your name is Alice."
async for chunk in session.send_stream("Tell me a fun fact about Alice."):
print(chunk.text_delta, end="", flush=True)
print()
Image generation
from flowith_webapi import ImageModel, AspectRatio
result = await client.generate_image(
"A ripe banana on a marble surface, studio lighting",
model=ImageModel.GEMINI_3_1_FLASH_IMAGE,
aspect_ratio=AspectRatio.SQUARE,
)
await result.image.save("./outputs")
print(result.image.url)
Video generation
from flowith_webapi import VideoModel
video = await client.generate_video(
"A cat walking on a sunny beach",
model=VideoModel.SEEDANCE_2_FAST,
timeout=300.0,
)
await video.save("./outputs")
print(video.video_url)
File upload
# Upload a local file and get a public CDN URL
record = await client.upload_file("my_game.html")
print(record.url) # https://r2-bucket.flowith.net/f/…/my_game.html
# Reference the file in a generation
r = await client.generate(
f"Review this HTML file: [[file:html|my_game.html|{record.url}]]"
)
# Rotate the access token (uses stored refresh token if not supplied)
if REFRESH:
await client.refresh_access_token()
Conversations
# List recent conversations
convs = await client.list_conversations(limit=10)
for c in convs:
print(c.conv_id, c.title)
# Read the flow-canvas node graph
nodes = await client.get_flow_nodes(conv_id)
for n in nodes:
print(n.node_type, n.text[:80])
# Rename / delete
await client.rename_conversation(conv_id, "New Title")
await client.delete_conversation(conv_id)
Raw SSE streams
# Generation-data stream (/user_stream/stream) — heartbeats filtered out
async for evt in client.stream_user_stream(timeout=60):
print(evt)
# Keepalive stream (/user_stream/sse) — heartbeats included
async for evt in client.stream_user_sse(timeout=60):
print(evt) # {"heartbeat": 1779127465201}
Credits & models
credits = await client.get_credits()
total = sum(c.remain_quota for c in credits)
print(f"Credits: {total:.2f}")
models = await client.list_models()
for m in models:
print(m.model_id, m.title)
# Active subscriptions
subs = await client.list_subscription_user_own()
print(len(subs))
# Profile
profile = await client.get_user_profile()
print(profile.language)
API reference
FlowithClient
| Method | Description |
|---|---|
generate(prompt, ...) |
Single-turn text generation (waits for full result) |
generate_stream(prompt, ...) |
Streaming text generation (yields token-by-token) |
generate_image(prompt, ...) |
Image generation |
generate_video(prompt, ...) |
Video generation |
generate_title(messages) |
Auto-generate conversation title |
upload_file(file_path, ...) |
Upload file to CDN |
start_conversation(...) |
Create ConversationSession for multi-turn use |
list_conversations(...) |
List user conversations |
get_conversation(conv_id) |
Fetch single conversation |
rename_conversation(conv_id, title) |
Rename conversation |
delete_conversation(conv_id) |
Soft-delete conversation |
get_flow_nodes(conv_id) |
Read flow-canvas node graph |
get_cooperators(conv_id) |
List conversation collaborators |
get_credits() |
Fetch credit balances |
get_credit_type() |
Return credit-type string |
get_auth_user() |
Return Supabase auth user object |
get_oauth_url(...) |
Build Supabase OAuth authorization URL |
refresh_access_token(refresh_token=None) |
Rotate access token |
logout(scope) |
Revoke auth session |
get_user_profile(user_id) |
Fetch profile row |
update_user_profile(**fields) |
Update profile fields |
list_user_upload_records(...) |
List uploaded file records |
list_subscription_user_own(...) |
List active subscription grants |
list_public_notices() |
List public notices |
list_flow_projects() |
List flow projects |
get_conv_novel_editor(conv_id) |
Fetch novel-editor content |
list_discuss(conv_id) |
List discussion entries |
get_oracle_stream(conv_id) |
Fetch oracle stream entries |
get_migration_info() |
Fetch migration version |
get_featurebase_status() |
Featurebase changelog status |
list_models(...) |
Browse model catalog |
list_templates(...) |
Browse prompt templates |
get_discord_invite() |
Return Discord invite URL |
get_daily_rewards_status() |
Daily rewards status |
claim_daily_reward() |
Claim today's reward |
check_campaign(model_id) |
Check campaign eligibility |
check_banana_campaign() |
Banana-2025 campaign status |
get_enterprise_dashboard() |
Enterprise dashboard info |
get_online_count() |
Current online user count |
upsert_online_session(payload) |
Upsert online session (RPC) |
remove_online_session(session_id) |
Remove online session (RPC) |
stream_user_stream(...) |
Stream live generation events from /user_stream/stream |
stream_user_sse(...) |
Stream keepalive heartbeat events from /user_stream/sse |
ConversationSession
| Method / Property | Description |
|---|---|
send(prompt, ...) |
Send message, await full reply |
send_stream(prompt, ...) |
Send message, stream reply token-by-token |
generate_image(prompt, ...) |
Generate image in this conversation |
generate_video(prompt, ...) |
Generate video in this conversation |
delete() |
Delete the server-side conversation |
.conv_id |
Conversation UUID (created lazily on first send) |
.model |
Session model ID |
.turn |
Number of turns sent so far |
Return types
| Type | Key fields |
|---|---|
GenerationOutput |
text, text_delta, node_id, conv_id, model |
ImageOutput |
images: list[GeneratedImage], image (first item) |
GeneratedImage |
url, model; .save(dir) downloads to directory |
VideoOutput |
video_url, model; .save(dir) downloads to directory |
FileRecord |
url, file_type |
ConversationRecord |
conv_id, title, preview |
FlowNode |
node_id, node_type, text, model |
UserCredits |
remain_quota, init_quota, sub_type |
ModelInfo |
model_id, title, media, tier |
TemplateRecord |
template_id, name, category, prompt |
CooperatorRecord |
user_id, user_name, role |
UserProfile |
user_id, language |
UserUploadRecord |
record_id, name, file_type, created_at |
SubscriptionUserOwn |
record_id, remain_quota, subscription_type |
PublicNotice |
notice_id, title, content |
FlowProject |
project_id, title, updated_at |
ConvNovelEditor |
conv_id, content |
DiscussRecord |
discuss_id, conv_id, content |
OracleStreamRecord |
record_id, workflow, is_stop |
MigrationInfo |
version |
Error handling
from flowith_webapi import (
AuthenticationError,
RateLimitError,
ValidationError,
APIError,
TimeoutError,
FileUploadError,
)
try:
r = await client.generate("Hello!")
except AuthenticationError:
print("Invalid or expired token.")
except RateLimitError as e:
print(f"Rate limited — retry in {e.retry_after_s}s")
except TimeoutError:
print("Generation timed out.")
except APIError as e:
print(f"API error {e.status_code}: {e}")
Logging
import flowith_webapi
flowith_webapi.set_log_level("DEBUG") # DEBUG | INFO | WARNING | ERROR
Related projects
- 1MinAI-API — sister project for 1min.AI
- Claude-API — unofficial Claude.ai client
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flowith_webapi-1.0.1.tar.gz.
File metadata
- Download URL: flowith_webapi-1.0.1.tar.gz
- Upload date:
- Size: 22.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
baabec70c11e303d39660ef55fe2a36d0670faccc6178c203274c2e24f72085e
|
|
| MD5 |
abfa6859fb569871268ec2b255a77165
|
|
| BLAKE2b-256 |
f4309e8108269c44eccd3b7a5ef3cd848041c2c1fc0bf8800a7bccf4ab6c91e7
|
Provenance
The following attestation bundles were made for flowith_webapi-1.0.1.tar.gz:
Publisher:
python-publish.yml on cyber-wojtek/Flowith-API
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flowith_webapi-1.0.1.tar.gz -
Subject digest:
baabec70c11e303d39660ef55fe2a36d0670faccc6178c203274c2e24f72085e - Sigstore transparency entry: 1593256835
- Sigstore integration time:
-
Permalink:
cyber-wojtek/Flowith-API@3775e62129996dfc7d8249d3039ec0e60f34e95c -
Branch / Tag:
refs/tags/v0.11 - Owner: https://github.com/cyber-wojtek
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@3775e62129996dfc7d8249d3039ec0e60f34e95c -
Trigger Event:
release
-
Statement type:
File details
Details for the file flowith_webapi-1.0.1-py3-none-any.whl.
File metadata
- Download URL: flowith_webapi-1.0.1-py3-none-any.whl
- Upload date:
- Size: 24.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94370daedf7fb198a01f1b245ed2d64ade0cb978c4e9e3605ecc83e7f6b04e30
|
|
| MD5 |
4501c3f86a7ce9f346e21783a64d1704
|
|
| BLAKE2b-256 |
6404b59ea37cd7db80ad27865150d96c808683f584574281432263c9150f411f
|
Provenance
The following attestation bundles were made for flowith_webapi-1.0.1-py3-none-any.whl:
Publisher:
python-publish.yml on cyber-wojtek/Flowith-API
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flowith_webapi-1.0.1-py3-none-any.whl -
Subject digest:
94370daedf7fb198a01f1b245ed2d64ade0cb978c4e9e3605ecc83e7f6b04e30 - Sigstore transparency entry: 1593256937
- Sigstore integration time:
-
Permalink:
cyber-wojtek/Flowith-API@3775e62129996dfc7d8249d3039ec0e60f34e95c -
Branch / Tag:
refs/tags/v0.11 - Owner: https://github.com/cyber-wojtek
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@3775e62129996dfc7d8249d3039ec0e60f34e95c -
Trigger Event:
release
-
Statement type: