model and an input. Short jobs finish inside the call. Long jobs, such as a 3D model or a video, give you a job id to follow.
Know the price first
POST /v1/estimate takes the same body as a job and prices it: the exact amount, or the most it can cost when atMost is true. It is free and needs no sign-in. The answer has these fields at the top level, with no wrapper.
Start a job
The answer is
{"job": {...}}:
201withstate: "done": the job finished.resultholds the work.202withstate: "running": still working. Followjob.id.202withstate: "queued": all your running slots are busy. The job is accepted at a held price and starts in order. Its id starts withq:; follow it like any job. Do not send it again.
model, the model that ran (also when you sent a default id such as 3d/default, so you can run the same model again), estimatedPrice, and price once Summer knows what it charged.
Running slots and the queue
Paid work has no rate limits. Each account runs 10 jobs at once at the start, and up to 40 as it buys more credits. Every job past that answersqueued and starts as soon as one of your running jobs finishes, in the order you sent them. Queued and running jobs hold their price against your paid balance and the app’s spend limit until they are charged, so the queue never spends past either one.
The only refusals are about money, at once and before any work starts: 402 no_credits (the paid balance) or 402 over_limit (the app’s own limit). A queued job that cannot start for 30 minutes while your queue stands still ends failed, with nothing charged.
Run many jobs: batches
POST /v1/batches takes up to 500 jobs in one call, all or none. Every job is priced first. When your paid credits and the app’s limit cover all of them, all are accepted (202) and run in order on your running slots. When they do not, nothing is accepted.
{"batch": {"id": "b_...", "estimatedPrice": "...", "jobs": [{"id": "q:...", "state": "queued", "model": "...", "estimatedPrice": "..."}]}}. Each job may carry its own idempotencyKey and webhookUrl. The header covers the whole batch: a retry with the same Idempotency-Key answers the batch it already made and never queues it twice.
A batch takes no quoteId. Each job is priced when the batch arrives and holds that price (estimatedPrice). To tell the person the total first, estimate each distinct request with POST /v1/estimate and add them up.
GET /v1/batches/{batchId} answers every job, in the order you sent them, with its state and, when done, its result, plus counts by state. Read it every 10 seconds or so until no job is queued or running, then download the files of the done jobs. Batches are kept 7 days.
A job with a wrong input refuses the whole batch with invalid_input; param names the job, such as jobs[3]. A batch that does not fit answers 402 with details: fits (how many jobs the money covers), jobs, totalUsd and shortUsd (how much more is needed). The code is no_credits when the paid balance is short and over_limit when the app’s limit is.
With the CLI, a batch is a JSONL file with one job per line: summer api run --batch jobs.jsonl --download assets.
Follow a job
pollAfterSeconds when the answer has it, or every 5 seconds. Job ids can hold a colon, such as gen:... or q:...; send them as they are. A queued job’s read answers the job it became once it starts. With the CLI, summer api jobs <id> --download follows the job and saves its files.
Summer API jobs never wait for an approval (
awaiting_approval is for other Summer jobs). Once accepted, queued or running, a Summer API job runs to the end: POST /v1/jobs/{id}/cancel answers unsupported for it. A job that fails costs nothing.
To get changes pushed instead, open the event stream. It sends a job event with {"job": {...}} each time the job changes, then done with {"state": ...} when the job reaches any final state, failed included. After about 4 minutes it sends timeout: open it again, or check the job.
Webhooks
PasswebhookUrl (public https) when you start a job, and Summer sends one signed POST there when the job finishes. First make a signing secret, once per account:
secret, shown only this once; keep it as SUMMER_WEBHOOK_SECRET. Making a new one stops the old one at once. Then add "webhookUrl": "https://example.com/summer-hook" to the POST /v1/jobs body; the job answer carries webhookId.
The POST body has the job’s final facts:
GET /v1/jobs/{jobId} for the files. Each POST carries Summer-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of t, a dot and the raw body, keyed with your secret. Check it before you trust the body:
2xx to confirm. Summer retries after 1, 5 and 30 minutes, then 1, 2, 4, 8 and 24 hours; answer 410 to stop. Summer-Webhook-Id is the same on every retry, so you can drop repeats. GET /v1/webhooks/deliveries lists your newest deliveries with every attempt.
The result
Every job has the sameresult: assets, one entry per file, each with assetId, fileUrl and thumbnailUrl. An image job with numImages above 1 has one entry per image. A job reads done once every file is in Summer storage, so every fileUrl is ready to download.
Assets made through the API are private: they show in your Summer library only. Each fileUrl is on Summer’s own storage and downloads without a sign-in, so share a URL only where the file may be seen. Download the file into your project, or keep the asset id: in Summer Engine your agent imports an asset by its id.
Send a picture
Image-to-3D, image editing and image-to-video take a public picture URL (imageUrl, referenceImageUrl). For a file on your computer, upload it first:
1
Start the upload
POST /v1/files with an empty JSON body ({}) answers with uploadUrl, the fields to send, expiresAt, and example: the whole upload as one curl command.2
Send the file
POST every field as a multipart form field, plus the file as
file, to uploadUrl. PNG, JPEG or WebP.3
Finish the upload
POST /v1/files/complete with three values from the answer to step 2: public_id as publicId, version, and signature as uploadSignature (not the signature in fields). Add format: png, jpg, jpeg or webp. The answer holds assetId, fileUrl, width and height. The picture is a private asset in your Summer library: pass fileUrl as imageUrl.
