Perl HTTP::Tiny: POST /v1/images on Sume with core modules only
Call Sume's image API from Perl with HTTP::Tiny and JSON::PP, both core modules; handles 200, 202 and errors, with a 40-second timeout.

Perl is still common on older servers where you cannot install much, and HTTP::Tiny is enough for this call.
The request is the same in every language: POST https://api.sume.com/v1/images with a bearer key from SUME_API_KEY, a JSON body with model, prompt and aspect_ratio, and a client timeout above the route's 30-second wait. The route defaults to mode: "sync", so the status code decides what you do next (docs read 2026-10-07):
Status codes to branch on
| Status | Meaning | What the code below does |
|---|---|---|
| 200 | Image finished inside the wait; data[].url holds the file | Prints the result |
| 202 | Wait expired (or mode is async or webhook); body is a job envelope with status_url and result_url | Prints the envelope; poll status_url and read result_url |
| 502 | The job failed inside the wait; error has code, retryable, next_action | Prints the error |
| 400, 404 | unsupported_parameter, or model_not_found | Prints the error |
Perl code
Save as gen.pl and run SUME_API_KEY=... perl gen.pl. HTTP::Tiny and JSON::PP ship with Perl; HTTPS also needs IO::Socket::SSL installed.
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP qw(encode_json decode_json);
my $key = $ENV{SUME_API_KEY} or die "SUME_API_KEY missing\n";
my $http = HTTP::Tiny->new(timeout => 40);
my $res = $http->post(
'https://api.sume.com/v1/images',
{
headers => {
'Authorization' => "Bearer $key",
'Content-Type' => 'application/json',
},
content => encode_json({
model => 'bytedance-seed/seedream-5-lite',
prompt => 'matte ceramic mug on a white sweep, soft shadow',
aspect_ratio => '16:9',
}),
}
);
if ($res->{status} == 200) {
my $out = decode_json($res->{content});
print "done: $out->{data}[0]{url}\n";
} elsif ($res->{status} == 202) {
print "queued, follow status_url: $res->{content}\n";
} else {
print "error $res->{status}: $res->{content}\n";
}Notes
HTTP::Tiny returns a hash with status and content; it does not die on non-2xx, so the three-way branch is explicit.
Reading $out->{data}[0]{url} is only valid on 200. On 202 the body is a job envelope, not an image list.
One bytedance-seed/seedream-5-lite image is $0.04375 billed (list $0.035 x 1.25). Prices here are Sume's list-times-1.25 figures. The catalog states the billable formula as "list × 1.25 → ceil usd cents", so treat the dollar amounts as the pre-rounding value and read the exact charge from billable_amount_usd_micros in the submit envelope. Failed or cancelled generations are not billed.
Sources: Sume Image API docs and Jobs and results (read 2026-10-07).
Sources
Related posts
More in Developers
- Filter /v1/videos/models for a request in Python: 12 s, 1080p, 9:16
Read supported durations, resolutions, ratios and audio from Sume's model listing and keep only ids that can run your request. A 25-line script that runs.
- Pin the model id in an ad test: sume/auto follows the catalog
sume/auto is a pure function of the request plus the catalog version, so two ad arms made weeks apart can land on different models. Pin an explicit id in tests.
- Poll hundreds of AI jobs without a thundering herd: jitter and budgets
Poll many Sume jobs without synchronized bursts: jitter, next_poll_after_seconds, per-plan read budgets, and the math on how much polling a plan can absorb.
- Polling 200 video jobs every 30 s is 400 calls a minute: do this
A poll loop copied to Sume's 30 s example turns 200 open jobs into 400 status calls a minute. Use next_poll_after_seconds, backoff, or a webhook plus sweep.
Written by Sume