Kotlin 2.4.20 allDistinctBy: unique Idempotency-Keys per batch
Kotlin 2.4.20 adds allDistinctBy. Use it to assert one Idempotency-Key per intent before a batch of Sume image submits, then post with java.net.http.

Short answer
Before you fire a batch of Sume submits from Kotlin, assert that every item carries a distinct Idempotency-Key. Kotlin 2.4.20, released 7 September 2026, introduced allDistinctBy(), so the guard is one line. The what's new page shows responses.allDistinctBy { it.participantId } as its uniqueness check.
The reason to care: Sume treats a repeated key with the same payload as the same operation and returns the original job, and rejects a repeated key with a different payload with 409 idempotency_conflict. Two different prompts that share a key in one batch would hit the conflict, or worse, one prompt would silently stand in for the other on a retry.
What 2.4.20 adds and what this post uses
Only one new standard-library function is used here, and it is experimental. The coroutine stack-trace recovery interface is also new in 2.4.20, which helps when a poll coroutine rethrows a Sume error, but this post keeps the sample dependency-free.
| Area | Item |
|---|---|
| Standard library | StackTraceRecoverable interface for coroutine exception recovery |
| Standard library | allDistinct(), allDistinctBy(), allEqual(), allEqualBy() |
| Kotlin/Native | Swift export: sealed classes map to Swift enums, plus generated Package.swift |
| Kotlin/Wasm | multi-module compilation modes: monolith, multimodule-open-world, multimodule-closed-world |
The batch submit
Keys are derived from the intent (date, shot name, version), not from a random value at send time. A random key per attempt would defeat retry safety, because the retry would look like a new job and bill again. The guard fails fast before any request leaves the machine. The release notes list these functions as experimental, so the sample opts in with @OptIn(ExperimentalStdlibApi::class).
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
data class Shot(val key: String, val prompt: String)
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val shots = listOf(
Shot("hero-2026-10-03-a", "Matte black bottle on marble"),
Shot("hero-2026-10-03-b", "Same bottle, soft window light"),
)
check(shots.allDistinctBy { it.key }) { "duplicate Idempotency-Key in batch" }
val http = HttpClient.newHttpClient()
for (s in shots) {
val body = """{"prompt":"${s.prompt}","mode":"async"}"""
val req = HttpRequest.newBuilder(URI.create("https://api.sume.com/v1/image-1.0/generate"))
.header("Authorization", "Bearer " + System.getenv("SUME_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", s.key)
.POST(HttpRequest.BodyPublishers.ofString(body)).build()
val res = http.send(req, HttpResponse.BodyHandlers.ofString())
println("${res.statusCode()} ${res.body()}")
}
}After the submits
Each response carries the job id, status_url and next_poll_after_seconds. Keep the id next to the key so a crash after the POST can be resolved by re-sending the same request with the same key rather than by guessing. The poll loop for the ids is on the Kotlin poll-loop post.
What Sume does and does not do
Sume returns the original job when a retry repeats the key and payload, and it returns 409 idempotency_conflict when the same key arrives with a different payload. Reuse a key only for the same operation and payload.
Sume does not de-duplicate on prompt text. Two submits with different keys and identical prompts are two paid jobs, so the uniqueness check above is about keys that mean the same intent, not about identical prompts.
Sources
Related posts
More in Developers
- Kubb for the Sume OpenAPI spec: Zod schemas and SWR hooks
Kubb parses an OpenAPI spec once and feeds plugins for TypeScript, Zod and SWR. Here is how that maps to Sume's 3.0.3 spec, jobs and the polling stop rule.
- LangChain4j StreamableHttpMcpTransport: protocol detection for Sume
LangChain4j probes the MCP protocol on connect, which costs one round trip. Pin protocolVersion and set timeouts before pointing it at Sume's hosted MCP server.
- last_frame without first_frame returns 400: kling-3 and Seedance fix
A frame_images list with only a last_frame is rejected with 400 unsupported_capability. Send a first_frame too; Kling 3.0 uses start and end frames only.
- Latin American Spanish text to speech: es or es-MX on Sume?
Sume's TTS language is a free string and its voice library tags voices with plain es. What that means for Mexican, Argentine or Spain Spanish, and how to test.
Written by Sume