- SDK
- Stateless Calls
SDK
Stateless Calls
Generate images and videos without a conversation-model call.
Choose a workflow:
| Workflow | How it works |
|---|---|
| Conversational image tools / video tools | Enable image_generation="auto" or video_generation="auto". Your chat model decides when to generate. |
| Stateless generation | Set operation="generate_image" or "generate_video". Your prompt and explicit inputs go to the selected media model, without an intermediary chat-model call. |
Both use POST /threads/messages, with the same model/provider and configuration names. Stateless generation currently supports openrouter.
- Stateless Image API: text-to-image, editing, multiple references, and image settings.
- Stateless Video API: text-to-video, first/last frames, video editing, every reference-modality combination, and video settings.
“Stateless” means no pre-created assistant/thread and no conversational context required. The API still stores messages and media metadata and returns thread_id and assistant_id. File contents live in S3. These examples require backend and SDK versions exposing operation; the SDK source must be released before installed packages gain this feature.
Start with one call
import asyncio
import os
from backboard import BackboardClient
async def main():
async with BackboardClient(
api_key=os.environ["BACKBOARD_API_KEY"], timeout=1900,
) as client:
result = await client.send_message(
"A blue robot watering plants.",
operation="generate_image",
image_model_provider="openrouter",
image_model_name="google/gemini-3.1-flash-image",
image_config={"resolution": "1K"},
)
for media in result.generated_media:
print(media["url"])
asyncio.run(main())
Install backboard-sdk with pip or npm. Python client timeouts are in seconds; JavaScript timeouts are in milliseconds. Run the Python recipes on the following pages inside the async with ... as client block above.
Shared request fields
| Field | Use |
|---|---|
operation | Required for stateless generation: generate_image or generate_video. Omit it for normal chat. |
content | Required text prompt, including when files are supplied. |
image_model_provider, image_model_name, image_config | Image model and optional settings. Use only for generate_image. |
video_model_provider, video_model_name, video_config | Video model and optional settings. Use only for generate_video. |
stream | false by default. Set true for SSE with keepalives and media events. |
metadata | Optional JSON object stored with the user message. |
thread_id, assistant_id | Optional existing context identifiers. Omit both for a one-shot call. They do not make stateless generation consume chat history. |
Do not pass a chat model (llm_provider / model_name), tools, voice, system prompts, thinking, search, or enabled memory controls. The generation tool switches (image_generation / video_generation) are not needed. Put provider routing inside the selected media config, not the chat-level openrouter field.
Files: explicit roles, not filenames in the prompt
| Field | Role |
|---|---|
input_image | Image-edit input or first video frame. |
input_last_frame | Last video frame; requires input_image. |
input_video | Source video for video editing/conditioning, if supported. |
input_references | General reference files. Accepted modalities depend on the output model. |
SDK values are local file paths, not URLs or document IDs. Raw HTTP uses multipart file fields with these exact names; repeat input_references for multiple files. Serialize the config object as a JSON string in multipart requests. Do not use the conversational files field here.
Upload order and filenames do not assign roles. A first frame and a general image reference are different inputs. See the video combination rules.
Read results or stream them
Non-streaming HTTP responses contain generated_media: each item includes document_id, media_type, mime_type, url, and file_size_bytes. Python exposes result.generated_media; JavaScript exposes message.generatedMedia in result.messages.
stream = await client.send_message(
"A blue robot watering plants.",
operation="generate_image",
image_model_provider="openrouter",
image_model_name="google/gemini-3.1-flash-image",
stream=True,
)
async for event in stream:
if event["type"] == "media_generated":
print(event["media"]["url"])
elif event["type"] == "error":
raise RuntimeError(event["error"])
elif event["type"] == "run_ended":
print(event["status"], event["thread_id"])
For JavaScript, pass stream: true and consume the result with for await. Event names and fields match Python. message_received includes the created thread/assistant IDs; successful streams end with run_ended. HTTP 200 alone is not proof that a stream succeeded.
Limits, errors, and lifecycle
| Outcome | What to do |
|---|---|
400: invalid input or configuration | Check the model, role combination, and config values. Do not silently drop inputs. |
402: insufficient credits | Fund the appropriate billing balance before retrying. |
413: upload too large | Reduce inputs to the deployment’s per-file and aggregate limits. Provider limits can be lower. |
503: capabilities unavailable | Model discovery failed before generation. |
502 or a streamed error | Generation/storage failed. Do not assume the provider incurred no cost. |
Generation is billed. Allow a long timeout and prefer streaming for videos. Do not automatically retry a timed-out generation: its upstream job may still be running. Disconnecting does not cancel stateless generation; the request continues to finish storage and billing. Active stateless generation runs return 409 from the chat cancellation endpoint.
This interface waits for completion; it is not a separate submit-and-poll API or a durable background queue. Reuse a returned thread_id for organization or a later normal chat request; each subsequent stateless generation still needs its own prompt and explicit inputs.