Python 3.14.8 Agent Completions: spend cap, schema, asyncio.run
Start an Agent Completion from Python 3.14.8 with asyncio.run, a required generation_spend_cap_usd, an output_schema and an Idempotency-Key, then poll the run.

Short answer
POST https://api.sume.com/v1/agent/completions returns 202 with an agrun_ receipt, not a chat reply, and it fails with 400 invalid_request unless you send generation_spend_cap_usd. Wrap the create and the poll in asyncio.run(main()); top-level await is not available in a script. Python 3.14.8 was released on 30 September 2026 according to the release page.
The Agent Completions docs describe the receipt, the scopes and the errors. This post wires them into a runnable script using only the standard library, with blocking calls pushed to threads by asyncio.to_thread.
Request fields that matter
Send exactly one of instruction or messages. The cap has no default, because an unattended agent has no interactive spend prompt and the cap is the substitute.
| Field | Required | Note |
|---|---|---|
| instruction or messages | one of | never both; assistant turns are rejected |
| generation_spend_cap_usd | yes | most you will spend on this one run |
| output_schema | no | binds the run's output to your schema |
| attachments | no | up to 30 images |
| communication.webhook_url | no | public HTTPS URL notified at the terminal status |
The script
The key needs agent_completions:write to create and agent_completions:read to poll. Keys created before Agent Completions shipped lack both and fail with 403 insufficient_scope; scopes cannot be added to an existing key, so create a new one. Service-account keys cannot create completions at all.
import asyncio, json, os, urllib.request
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"], "Content-Type": "application/json"}
def call(method, url, payload=None, extra=None):
data = json.dumps(payload).encode() if payload else None
req = urllib.request.Request(url, data=data, method=method, headers={**H, **(extra or {})})
with urllib.request.urlopen(req, timeout=30) as res:
return json.load(res)["data"]
SCHEMA = {"name": "caption", "schema": {"type": "object", "additionalProperties": False,
"properties": {"caption": {"type": "string"}}, "required": ["caption"]}}
async def main():
run = await asyncio.to_thread(call, "POST", "https://api.sume.com/v1/agent/completions", {
"instruction": "Write a 12-word caption for a matte black bottle on marble.",
"generation_spend_cap_usd": 1,
"output_schema": SCHEMA,
}, {"Idempotency-Key": "caption-2026-10-03-001"})
url = "https://api.sume.com/v1/agent-runs/" + run["id"]
while run["status"] in ("queued", "processing"):
await asyncio.sleep(3)
run = await asyncio.to_thread(call, "GET", url)
print(run["status"], json.dumps(run["output"]))
asyncio.run(main())Reading the finished run
A completed run fills output. With an output_schema, the output is parsed against it after the run completes. Generated media comes back as durable media.sume.com HTTPS URLs in output.images, output.videos, output.audio and output.files. Run states match Action runs: queued, processing, completed, failed, canceled.
To stop a run in flight call POST /v1/agent-runs/{id}/cancel. A 404 agent_run_not_found means the id is unknown, belongs to another account, or is an Action or Format run id, which does not resolve on this route.
What Sume does and does not do
Sume replays an Idempotency-Key with the original receipt and idempotency_hit: true, and returns 409 idempotency_conflict if the payload differs. Every completion runs in a fresh thread.
Sume does not stream Agent Completions, does not offer an OpenAI-compatible choices[] response, does not continue a prior thread with thread_id, and accepts image attachments only. For push delivery set communication.webhook_url and verify the agent.run.terminal event.
Sources
Related posts
More in Developers
- Python 3.14.8 urllib: poll a Sume job with only the stdlib
Python 3.14.8 shipped 30 September 2026. A urllib-only loop for GET /v1/jobs/:id/status with a 20-minute client deadline and no third-party packages.
- Python 3.15 json array_hook: freeze a Sume job result
Python 3.15 adds array_hook to json.load and json.loads. Pair it with frozendict to hold a completed Sume job result as deeply immutable data you can share.
- Python 3.15 lazy import in a Sume webhook handler: what to defer
Python 3.15 adds the lazy import keyword. In a Sume webhook receiver with a 10-second attempt budget, defer only the modules the signature check does not need.
- Python 3.15 UTF-8 default: still verify Sume webhooks on raw bytes
Python 3.15 makes UTF-8 the default for open() without an encoding. It does not change that a Sume signature covers raw body bytes, so verify before any parse.
Written by Sume