Skip to main content

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'] 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.

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)

Source distribution for sume-client 0.1.0
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)

Table of built distributions (wheels) for sume-client 0.1.0
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.1.0 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