Skip to main content

Sume Python Client

The official Python SDK for the Sume Open API, with a synchronous and an asynchronous client.

Installation

pip install sume-client

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'] is COMPLETED, and result['result']['artifacts'] lists the outputs, each with a url, a type and a content_type.
  • A failed or canceled job returns its final status from GET /v1/jobs/{id}/status: result['status'] is FAILED or CANCELED, and result['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.

Development

pip install -e . pytest pytest-asyncio pytest-mock pillow
pytest

The tests run against a fake Sume API and spend nothing.

License

MIT. Copyright (c) 2026 Sume Labs. See LICENSE and NOTICE. Third-party notices for installed dependencies are in THIRD-PARTY-NOTICES.txt.

Metadata

Release files for sume-client 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sume-client 0.1.1
File Size Uploaded Provenance Actions
sume_client-0.1.1.tar.gz 15.2 kB Without provenance Details for sume_client-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sume-client 0.1.1
File Interpreter ABI Platform Actions
sume_client-0.1.1-py3-none-any.whl Python 3 none any Details for sume_client-0.1.1-py3-none-any.whl

Total release size: 30.9 kB

Release files / sume_client-0.1.1.tar.gz

Download URL sume_client-0.1.1.tar.gz
Size 15.2 kB
Tags Source
SHA-256 checksum
How to use checksums
055b715d40e8cd615c25bf3468323f33d480031734ba0e27307bf4299a0dae72
BLAKE2b-256 checksum
How to use checksums
ee905c70faf8808cf036da09183c960f390ee99a6997686238ed33f566582175
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.1-py3-none-any.whl

Download URL sume_client-0.1.1-py3-none-any.whl
Size 15.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
50660a6df6f3e67a192568a124240b4386e9d744c7dd72ff6f3b7b0fec3d5fa9
BLAKE2b-256 checksum
How to use checksums
42e05dfff10e07e19d0834d073fb0da7a1fde0a670f524f0982f3cbbe5022142
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.1

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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page