Sume Python Client
The official Python SDK for the Sume Open API, with a synchronous and an asynchronous client.
Installation
sume-client is not on PyPI yet. Install it from GitHub:
pip install git+https://github.com/sumelabs/sume-client.git
Authentication
Before using the client, set your API key as an environment variable:
export SUME_API_KEY="sume_live_..."
Get your key from open.sume.com → API keys. The client sends it as Authorization: Bearer <key> to https://api.open.sume.com.
Quick Start
Synchronous:
import sume_client
result = sume_client.subscribe(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
}
)
print(result['result']['artifacts'][0]['url'])
Asynchronous:
import asyncio
import sume_client
async def main():
result = await sume_client.subscribe_async(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
}
)
print(result['result']['artifacts'][0]['url'])
asyncio.run(main())
gpt-image-2 at quality: 'low' costs under a cent. List open models gives every model's price, and Estimate a price prices a request before you send it.
Usage Patterns
Pattern 1: Simple Submit and Wait
subscribe() submits the job and returns when it has ended:
Synchronous:
import sume_client
result = sume_client.subscribe(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
}
)
print(result['result']['artifacts'][0]['url'])
Asynchronous:
import asyncio
import sume_client
async def main():
result = await sume_client.subscribe_async(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
}
)
print(result['result']['artifacts'][0]['url'])
asyncio.run(main())
Pattern 2: Submit and Track Progress
submit() returns a request controller at once, and poll_request_status() yields each status until the job ends. webhook_url is optional: Sume POSTs the outcome to that public HTTPS URL when the job ends (Webhooks).
Synchronous:
import sume_client
request_controller = sume_client.submit(
'gpt-image-2',
arguments={
'prompt': 'Football ball',
'quality': 'low',
'aspect_ratio': '16:9',
},
webhook_url='https://example.com/webhook'
)
for status in request_controller.poll_request_status():
if isinstance(status, sume_client.Queued):
print('Queued')
elif isinstance(status, sume_client.InProgress):
print('In progress')
elif isinstance(status, sume_client.Completed):
print('Completed')
elif isinstance(status, (sume_client.Failed, sume_client.NSFW, sume_client.Cancelled)):
print('Oops!')
result = request_controller.get()
print(result['result']['artifacts'][0]['url'])
Asynchronous:
import asyncio
import sume_client
async def main():
request_controller = await sume_client.submit_async(
'gpt-image-2',
arguments={
'prompt': 'Football ball',
'quality': 'low',
'aspect_ratio': '16:9',
},
webhook_url='https://example.com/webhook'
)
async for status in request_controller.poll_request_status():
if isinstance(status, sume_client.Queued):
print('Queued')
elif isinstance(status, sume_client.InProgress):
print('In progress')
elif isinstance(status, sume_client.Completed):
print('Completed')
elif isinstance(status, (sume_client.Failed, sume_client.NSFW, sume_client.Cancelled)):
print('Oops!')
result = await request_controller.get()
print(result['result']['artifacts'][0]['url'])
asyncio.run(main())
Pattern 3: Submit with Callbacks
on_enqueue gets the request ID once the job exists, and on_queue_update gets every status:
Synchronous:
import sume_client
def on_enqueue(request_id):
print(f'Request {request_id} was enqueued')
def on_status_update(status):
print(f'Status: {status}')
result = sume_client.subscribe(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
},
on_enqueue=on_enqueue,
on_queue_update=on_status_update
)
Asynchronous:
import asyncio
import sume_client
def on_enqueue(request_id):
print(f'Request {request_id} was enqueued')
def on_status_update(status):
print(f'Status: {status}')
async def main():
await sume_client.subscribe_async(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
},
on_enqueue=on_enqueue,
on_queue_update=on_status_update
)
asyncio.run(main())
Pattern 4: Manage Existing Requests
Keep the request controller, or only the request ID, and check, cancel or wait for the job later:
Synchronous:
import sume_client
request_controller = sume_client.submit(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
},
webhook_url='https://example.com/webhook'
)
status = request_controller.status()
try:
request_controller.cancel()
except sume_client.SumeClientError as error:
print(error.code)
result = request_controller.get()
status() checks the status once, cancel() asks Sume to cancel the job, and get() waits for the job to end and returns its result.
Or by request ID:
import sume_client
request_id = 'job_7513b52d-3669-4ffe-b317-8b02000715eb'
status = sume_client.status(request_id=request_id)
try:
sume_client.cancel(request_id=request_id)
except sume_client.SumeClientError as error:
print(error.code)
result = sume_client.result(request_id=request_id)
Asynchronous:
import asyncio
import sume_client
async def main():
request_controller = await sume_client.submit_async(
'gpt-image-2',
arguments={
'prompt': 'A serene lake at sunset with mountains',
'quality': 'low',
'aspect_ratio': '16:9',
},
webhook_url='https://example.com/webhook'
)
status = await request_controller.status()
try:
await request_controller.cancel()
except sume_client.SumeClientError as error:
print(error.code)
result = await request_controller.get()
asyncio.run(main())
Or by request ID:
import asyncio
import sume_client
async def main():
request_id = 'job_7513b52d-3669-4ffe-b317-8b02000715eb'
status = await sume_client.status_async(request_id=request_id)
try:
await sume_client.cancel_async(request_id=request_id)
except sume_client.SumeClientError as error:
print(error.code)
result = await sume_client.result_async(request_id=request_id)
asyncio.run(main())
Sume cancels a job only before generation starts, and the image and video routers hand the job to the model inside submit(). So cancel() usually raises SumeClientError with code job_generation_already_started, or job_not_cancelable once the job has ended. A canceled job is refunded.
File Uploads
Put a local file in Sume storage and pass its URL to a model. Each upload returns a public HTTPS URL on media.sume.com that you pass in a model's input field, such as image_urls, image_url, video_url or audio_url (Input media). Uploading is free.
Upload bytes
import sume_client
image_path = 'path/to/example.jpeg'
content_type = 'image/jpeg'
with open(image_path, 'rb') as f:
data = f.read()
url = sume_client.upload(data, content_type)
Upload by path
import sume_client
image_path = 'path/to/example.jpeg'
url = sume_client.upload_file(image_path)
Upload PIL Images
from PIL import Image
import sume_client
image = Image.open('example.jpeg')
url = sume_client.upload_image(image, format='jpeg')
Async Uploads
Each upload function has an _async twin:
import sume_client
url = await sume_client.upload_async(data, content_type='image/jpeg')
url = await sume_client.upload_file_async('path/to/example.jpeg')
url = await sume_client.upload_image_async(image, format='jpeg')
Use an upload
import sume_client
url = sume_client.upload_file('path/to/example.jpeg')
result = sume_client.subscribe(
'seedance-2.5',
arguments={
'prompt': 'The camera slowly pushes in',
'image_url': url,
'duration': 5,
'resolution': '480p',
}
)
print(result['result']['artifacts'][0]['url'])
Models and Arguments
The first argument is a model id from the open catalog, such as gpt-image-2, nano-banana-pro, seedance-2.5 or kling-3. List open models (GET https://api.open.sume.com/v1/open/catalog, no key needed) lists them all.
The client reads the model's kind once per client from GET /v1/open/catalog/{model_id}. Image models go to POST /v1/image-router/generate; video, lip-sync and caption models go to POST /v1/video-router/generate. arguments are the model's own fields (prompt, quality, aspect_ratio, duration, resolution, image_url and so on), described in Generate an image and Generate a video.
Results
subscribe(), result() and get() wait for the job to end and return the response's data:
- A completed job returns its result from
GET /v1/jobs/{id}/result:result['status']isCOMPLETED, andresult['result']['artifacts']lists the outputs, each with aurl, atypeand acontent_type. - A failed or canceled job returns its final status from
GET /v1/jobs/{id}/status:result['status']isFAILEDorCANCELED, andresult['job']['error']says why. Its price is refunded.
A request the model refuses at once raises SumeClientError from submit() or subscribe() instead, for example code provider_submission_failed.
Statuses
status() and poll_request_status() return one of these classes. DONE_STATUSES holds the last four.
| Class | Job's sume_status |
|---|---|
Queued |
queued |
InProgress |
processing |
Completed |
completed |
Failed |
failed |
NSFW |
failed, with error.public_reason content_policy_rejected |
Cancelled |
canceled |
Polling asks for the status every 2 seconds, the API's recommended_poll_interval_seconds. Pass poll_request_status(delay=...) to change it.
Errors
Every answer outside 2xx raises sume_client.SumeClientError. Its message is the API's message, with the failing fields of an invalid_request. status_code, code, request_id and details carry the rest of the error:
import sume_client
try:
sume_client.subscribe('gpt-image-2', arguments={'prompt': 'A lake', 'quality': 'low'})
except sume_client.SumeClientError as error:
print(error.status_code, error.code, error.request_id, error)
A missing key raises sume_client.CredentialsMissedError on the first call.
A request to the API is tried up to 3 times when the answer is 408, 429, 500, 502, 503 or 504, unless the error says retryable: false, and when the connection fails or times out. The client waits 0.2 s and then 0.4 s between tries, or longer when the API's retry-after asks for it (up to 60 s). After the last try, a connection failure raises the httpx error. Every generate request carries a new Idempotency-Key, and its retries reuse it, so the client's own retries never create a second, billed job.
The upload PUT goes to storage once, without your key. If storage refuses it, SumeClientError names the storage error code and leaves out the presigned URL.
Clients
The module functions use two default clients, sume_client.sync_client and sume_client.async_client. Make your own to set the key, host or timeout in code:
from sume_client import AsyncClient, SyncClient
client = SyncClient(api_key='sume_live_...', base_url='https://api.open.sume.com', timeout=90.0)
result = client.subscribe('gpt-image-2', arguments={'prompt': 'A lake', 'quality': 'low'})
async_client = AsyncClient(api_key='sume_live_...')
Every call goes to base_url. The client builds the job URLs from it and the request ID, and does not follow the hosts in API responses.
AsyncClient keeps one connection pool per event loop, so one client can serve several asyncio.run() calls and several threads.
Coming from higgsfield-client
sume-client keeps higgsfield-client's module functions, clients, request controllers, status classes and retry strategies, with the same arguments. What changes:
| higgsfield-client | sume-client | |
|---|---|---|
| Package | higgsfield_client |
sume_client |
| Host | https://api.higgsfield.ai |
https://api.open.sume.com |
| Key | HF_KEY, or HF_API_KEY + HF_API_SECRET |
SUME_API_KEY |
| Header | Authorization: Key <key> |
Authorization: Bearer <key> |
| First argument | an application path | a model id |
| Webhook | ?hf_webhook= |
mode: webhook and webhook_url in the body |
| Result | the status response | the result's data (result['result']['artifacts']) |
| Error | HiggsfieldClientError |
SumeClientError, with status_code, code, request_id, details |
| Upload | content_type |
content_type and size_bytes |
| Polling | every 0.5 s | every 2 s |
| Cancel | a queued request | a job the model has not started; router jobs start inside submit() |
| Agent API | client.agents |
not available: Open keys call the model API only |
Development
pip install -e . pytest pytest-asyncio pytest-mock pillow
pytest
The tests run against a fake Sume API and spend nothing.
License
MIT. See LICENSE. NOTICE credits higgsfield-client (Apache-2.0), whose public interface this client follows.
Metadata
Release files for sume-client 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sume_client-0.1.0.tar.gz | 16.1 kB | Without provenance | Details for sume_client-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sume_client-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for sume_client-0.1.0-py3-none-any.whl |
Total release size: 32.5 kB
Release files / sume_client-0.1.0.tar.gz
| Download URL | sume_client-0.1.0.tar.gz |
|---|---|
| Size | 16.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c84958f2742e61add890d43adf719ea73e3426566a4a1fc65b1954bd1be571fe
|
|
BLAKE2b-256 checksum How to use checksums |
6f067e897eb5d4371c678a5607fe00dd70b76c951ab3cef4a84ca2598a068eff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|
Release files / sume_client-0.1.0-py3-none-any.whl
| Download URL | sume_client-0.1.0-py3-none-any.whl |
|---|---|
| Size | 16.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59512dfb32f2f207d615d8a59f45101b21f969acfcc3ebf4a9da37af014aa2db
|
|
BLAKE2b-256 checksum How to use checksums |
57799d86e77c410aa29b0ce903a7c694e5b2c86d329d20d0ce095e7ddf3d02e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.8
|
Security for sume-client 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.