Skip to main content
Every model runs the same way: one job with a 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": {...}}:
  • 201 with state: "done": the job finished. result holds the work.
  • 202 with state: "running": still working. Follow job.id.
  • 202 with state: "queued": all your running slots are busy. The job is accepted at a held price and starts in order. Its id starts with q:; follow it like any job. Do not send it again.
The job also carries 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 answers queued 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.
The answer is {"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

Check again after 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

Pass webhookUrl (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:
The answer holds 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:
Then 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:
Answer 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 same result: 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.
The exact fields of each call are in the API reference: Start a file upload and Finish a file upload.