Infery and for AsyncInfery; every type it names is linked to
its own section below, with each field’s type and whether it is required.
The two clients are twins by construction: the same names, the same parameters and
the same defaults, with async def and Iterator[X] becoming AsyncIterator[X].
That is enforced twice — by tests/test_surface_parity.py in the package, and by
this generator, which refuses to write a page where a method’s twin is missing or
its signature differs by anything else.
A method returning Iterator[X] is a generator: nothing is requested until you
start iterating, so an unconsumed one spends nothing, and its async twin is consumed
with async for rather than await. Everything else is an ordinary call, awaited on
AsyncInfery.
Two things this page cannot tell you. The parameters that belong to a MODEL rather
than to the endpoint — an image model declares its own sizes, a speech model its
voices — come from _infery.allowed_params on
GET /v1/models and are rendered per model on the
live catalogue; every method takes **params so they
pass straight through. And the operational limits — retry policy, timeouts, poll
intervals — are on the Python SDK page, because they are decisions
rather than shapes.
The two clients
api_key is required and is never read from the environment: a client built without one raises before it makes a request. A client given an http_client never closes it — the caller owns what the caller made. Infery closes its own through close() or a with block; AsyncInfery through await aclose() or async with.
client.chat.completions
chat.completions.create()
POST /v1/chat/completions — Create chat completion
Request shape: ChatCompletionParams — what **params accepts beyond the named arguments above.
Types: ChatCompletionResult
chat.completions.stream()
POST /v1/chat/completions — Create chat completion
Request shape: ChatCompletionParams — what **params accepts beyond the named arguments above.
A generator: nothing is sent until the first chunk is pulled.
The LAST chunk before [DONE] carries credits_used and an EMPTY choices — it is yielded like any other rather than swallowed, because it is the only place the cost of a streamed call appears. A caller that breaks out early therefore never learns what the call cost; that is the caller’s choice to make, not this method’s to make for them.
A cut stream raises StreamTruncatedError after delivering the chunks that did arrive — see _core.sse.
Types: ChatCompletionChunk
client.embeddings
embeddings.create()
POST /v1/embeddings — Create embeddings
Request shape: EmbeddingParams — what **params accepts beyond the named arguments above.
Types: EmbeddingResult
client.media
media.generate()
background=True to get a job_id immediately and collect it with wait() — for video that is a bare submit, and for everything else it is the gateway’s own 504-with-a-job-id, caught here and handed over as a result instead of an exception.
on_progress fires for modality="video", which is the only modality whose generation this module polls itself. The other five poll only when the gateway DEFERS, and that poll happens inside the per-modality resource — images.generate, music.generate, three_d.generate, videos.upscale, audio.speech.create — none of which takes a callback. wait(on_progress=...) reports for all six; generate does not, and the residue is recorded in the task report rather than left for a caller to discover from silence.
Everything past modality/model is forwarded as given: prompt for most modalities, input/voice for audio, image_url/video_url for upscale and the image-to-* models. What a call accepts is a property of the MODEL, not of this SDK — the authoritative list is _infery.allowed_params on GET /v1/models — so a parameter a model gained yesterday works today without an SDK release. The per-modality resource still applies its own named-argument checks, so a missing prompt is refused here before a request is sent.
Types: MediaModality · MediaProgress · MediaResult
media.wait()
background=True.
modality is REQUIRED and cannot be inferred: video jobs live at GET /v1/videos/generations/{id} and every other modality’s at GET /v1/images/jobs/{id}, and a job id does not say which it is.
on_progress fires for EVERY modality, with the status and the percentage the polled job reported. It did not always: poll_job took no callback where poll_video had one since Task 12, so this keyword was accepted and then silently dropped for five of the six modalities. The callback was added to poll_job/apoll_job and forwarded through Jobs.wait rather than faked here — one shared loop reporting, not five callers each growing their own.
Types: MediaModality · MediaProgress · MediaResult
client.images
images.generate()
POST /v1/images/generations — Create image generation
Request shape: ImageGenerateParams — what **params accepts beyond the named arguments above.
Generates images, waiting for the result.
A slow model answers 504 with a job id and keeps working, so this polls GET /v1/images/jobs/{id} to completion and returns the finished JobStatus: that arm of the union is a COLLECTED result, never a handle.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: ImageResponse · JobStatus
images.edit()
POST /v1/images/edits — Edit an existing image
Request shape: ImageEditParams — what **params accepts beyond the named arguments above.
Edits an image, optionally through a mask.
image/mask are raw bytes; the base64 encoding and the MIME sniff happen here, because this endpoint takes JSON with image_base64 rather than the multipart the OpenAI SDK posts.
A slow model answers 504 with a job id and keeps working, so this polls GET /v1/images/jobs/{id} to completion and returns the finished JobStatus: that arm of the union is a COLLECTED result, never a handle.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: ImageResponse · JobStatus
images.upscale()
POST /v1/images/upscale — Upscale an image
Request shape: ImageUpscaleParams — what **params accepts beyond the named arguments above.
Upscales an image by URL.
Needs an upscale-modality model that accepts an image: a VIDEO upscaler on this route is refused by the gateway with a 400 that points at videos.upscale, and the model slug alone does not say which kind it is.
A slow model answers 504 with a job id and keeps working, so this polls GET /v1/images/jobs/{id} to completion and returns the finished JobStatus: that arm of the union is a COLLECTED result, never a handle.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: ImageUpscaleResult · JobStatus
client.videos
videos.submit()
POST /v1/videos/generations — Create video generation
Request shape: VideoSubmitParams — what **params accepts beyond the named arguments above.
Types: VideoSubmitResult
videos.retrieve()
GET /v1/videos/generations/{job_id} — Get video generation status
Types: VideoJobStatus
videos.generate()
VideoSubmitResult · VideoJobStatus
videos.wait()
submit.
The twin of Jobs.wait for the ONE job kind Jobs.wait does not serve: video generation lives at GET /videos/generations/{id} with its own (disjoint) status shape, and every other modality’s job at GET /images/jobs/{id}.
A thin passthrough for the same reason Jobs.wait is one — poll_video owns the deadline, the terminal-state decision and the failure decision, and this must not grow a second copy of any of them. It exists so that media.wait(modality="video") can COMPOSE a resource like its five siblings do rather than reach past the resource layer into _core.poll.
Types: VideoJobStatus
videos.upscale()
POST /v1/video/upscale — Upscale a video
Request shape: VideoUpscaleParams — what **params accepts beyond the named arguments above.
Upscales a video by URL.
The one video route that defers through the shared media job endpoint rather than /videos/generations — video GENERATION polls its own endpoint, which is why generate takes on_progress and this does not.
A slow model answers 504 with a job id and keeps working, so this polls GET /v1/images/jobs/{id} to completion and returns the finished JobStatus: that arm of the union is a COLLECTED result, never a handle.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: VideoUpscaleResult · JobStatus
client.music
music.generate()
POST /v1/music/generations — Generate music from text prompt
Request shape: MusicGenerateParams — what **params accepts beyond the named arguments above.
Generates a track and waits for it. stream() is the same route read as SSE.
A slow model answers 504 with a job id and keeps working, so this polls GET /v1/images/jobs/{id} to completion and returns the finished JobStatus: that arm of the union is a COLLECTED result, never a handle.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: MusicGenerateResult · JobStatus
music.stream()
POST /v1/music/generations — Generate music from text prompt
Request shape: MusicGenerateParams — what **params accepts beyond the named arguments above.
Progress events while the track renders, then ONE terminal event.
Three shapes arrive, each tagged with type: progress repeatedly, then exactly one of completed (carrying credits_used — the only place a streamed generation’s cost appears) or error. The error frame is a real failure delivered as a frame, because the gateway has already flushed SSE headers by then and cannot answer with an HTTP status; a caller that ignores type sees a successful, empty stream. Neither is swallowed here.
No deferral handling, unlike generate: a stream cannot hand back a job id, and music.controller.ts:283 keeps a streaming request on the inline path for exactly that reason.
Types: MusicStreamEvent
client.audio.speech
audio.speech.create()
POST /v1/audio/speech — Text-to-speech
Request shape: SpeechParams — what **params accepts beyond the named arguments above.
Synthesises speech and returns the audio with its content_type and credits_used.
POST /v1/audio/speech answers with an audio body rather than JSON, so there is no envelope and nothing to link to. A deferral is still collected: the worker stores the audio, the finished job carries a signed URL, and that URL is fetched through this client’s own transport — so a caller always gets audio and never a job.
Returns SpeechResult, not bare bytes. The bytes alone dropped the response object, and with it the Content-Type a caller needs to save the file and the x-credits-used that is the only statement of what the call cost — .audio is the same payload the old return value was.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: SpeechResult
client.audio.transcriptions
audio.transcriptions.create()
POST /v1/audio/transcriptions — Speech-to-text
Request shape: TranscriptionParams — what **params accepts beyond the named arguments above.
Transcribes audio. Sends MULTIPART file/filename, not the JSON file_base64 body TranscriptionParams documents.
Returns a bare str when response_format is text, srt or vtt — those formats answer with the document itself — and a TranscriptionResult otherwise.
The only deferrable method whose return union omits JobStatus, and that is recorded rather than tidied into consistency: a collected job is mapped back into a TranscriptionResult from the job’s payload, because the deliverable is TEXT and the durable path stores it as text rather than as a file artifact. sdks/typescript/src/resources/audio.ts reads the same field for the same reason.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier. Python offers background=True here where TypeScript deliberately does not, which is coherent for exactly that reason.
Types: TranscriptionResult
client.audio.transformations
audio.transformations.create()
POST /v1/audio/transformations — Transform audio (voice-changer, stem separation, video→audio extraction, …)
Request shape: AudioTransformationParams — what **params accepts beyond the named arguments above.
Voice changing, stem separation and video-to-audio extraction, all on one route.
audio_url is named because most models on this route take audio; a video-input model wants video_url through **params instead.
A slow model answers 504 with a job id and keeps working, so this polls GET /v1/images/jobs/{id} to completion and returns the finished JobStatus: that arm of the union is a COLLECTED result, never a handle.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: AudioTransformationResult · JobStatus
client.three_d
three_d.generate()
POST /v1/3d/generations — Generate a 3D model from text or an image
Request shape: ThreeDGenerateParams — what **params accepts beyond the named arguments above.
Generates a 3D asset from a prompt, an image, or both.
At least one of prompt/image_url is required and the check happens BEFORE the request is built, so a call missing both costs nothing.
A slow model answers 504 with a job id and keeps working, so this polls GET /v1/images/jobs/{id} to completion and returns the finished JobStatus: that arm of the union is a COLLECTED result, never a handle.
background=True does NOT return the handle: the gateway’s JobDeferredError is re-raised and its job_id is what jobs.wait() collects. A deliberate divergence from the TypeScript SDK, where background: true returns a result carrying job_id — here the exception is the carrier.
Types: ThreeDGenerateResult · JobStatus
client.files
files.create()
POST /v1/files — Upload a file
Request shape: FileCreateParams — what **params accepts beyond the named arguments above.
Types: FileObject
files.list()
GET /v1/files — List uploaded files
Request shape: FileListParams — what **params accepts beyond the named arguments above.
Types: FileListResult
files.retrieve()
GET /v1/files/{file_id} — Retrieve file metadata
Types: FileObject
files.content()
GET /v1/files/{file_id}/content — Download file contents
The bytes and their content_type, not JSON. A caller wanting a stream uses the transport.
.content is the payload the bare-bytes return used to be; the type exists because dropping the response also dropped the Content-Type of a file this SDK just handed the caller to save.
Types: FileContentResult
files.delete()
DELETE /v1/files/{file_id} — Delete a file
Types: FileDeleteResult
client.models
models.list()
GET /v1/models — List available models
Request shape: ModelListParams — what **params accepts beyond the named arguments above.
Types: ModelListResult
models.estimate()
POST /v1/models/{slug}/estimate — Estimate credits for a model request
Request shape: ModelEstimateParams — what **params accepts beyond the named arguments above.
Types: ModelEstimateResult
client.tools
tools.list()
GET /v1/tools — List available workflow capabilities
Types: ToolListResult
client.capabilities
capabilities.run()
POST /v1/capabilities/{capability_id}/run — Run a single capability synchronously
Request shape: CapabilityRunParams — what **params accepts beyond the named arguments above.
Types: CapabilityRunResult
client.workflows
workflows, with runs and templates hanging off it.
workflows.list()
GET /v1/workflows — List workflows
Request shape: WorkflowListParams — what **params accepts beyond the named arguments above.
offset, not after: this route pages by offset, and its response carries total/limit/offset. An earlier draft of the plan said after — that is the Files convention, and the two routes really do differ.
Types: WorkflowListResult
workflows.create()
POST /v1/workflows — Create a reusable workflow
Request shape: WorkflowCreateParams — what **params accepts beyond the named arguments above.
Types: WorkflowWriteResult
workflows.retrieve()
GET /v1/workflows/{workflow_id} — Get workflow (latest or specific version)
Request shape: WorkflowRetrieveParams — what **params accepts beyond the named arguments above.
version is a QUERY parameter here, not a body field — and it is spelled version, not pipeline_version: the run/estimate BODIES carry the un-renamed spelling, the query does not.
Types: WorkflowResult
workflows.update()
PUT /v1/workflows/{workflow_id} — Update workflow (creates new version if definition changed)
Request shape: WorkflowUpdateParams — what **params accepts beyond the named arguments above.
A PARTIAL update — UpdatePipelineDto declares no required array — so an argument left at None is not sent rather than sent as null.
Answers the SAME WorkflowWriteResult as create: both operations resolve to PipelineWriteResultDto on the spec side, so there is one type and not a Create/Update pair implying two shapes.
Types: WorkflowWriteResult
workflows.delete()
DELETE /v1/workflows/{workflow_id} — Soft-delete workflow
delete, not del: TypeScript needed del because delete is reserved there.
Types: WorkflowDeleteResult
workflows.estimate()
POST /v1/workflows/estimate — Dry-run cost estimate for a workflow definition (no execution)
Request shape: WorkflowEstimateParams — what **params accepts beyond the named arguments above.
A QUOTE, not a hold. Nothing is reserved and no run is capped by it — runs.create settles per step regardless of what this returned.
Types: WorkflowEstimateResult
client.workflows.runs
workflows.runs.create()
POST /v1/workflows/runs — Run a workflow (sync default; mode=async returns job id; mode=stream returns SSE)
Request shape: WorkflowRunCreateParams — what **params accepts beyond the named arguments above.
Start a run and wait for it (mode="sync", the default), or queue it (mode="async").
THIS SPENDS CREDITS. Every step settles against the wallet as it dispatches; there is no pre-flight balance gate and no cap derived from estimate(), which is a quote. A run that fails half way has still paid for the steps that ran — send its id as resume_from_run_id to continue from the step that failed rather than re-billing what already ran.
Supply exactly one of workflow_id or definition: both is a 400 (ambiguous_definition), neither is a 400 (missing_definition). The SDK does not pre-empt either — the gateway’s refusal names which one it was, and a client-side copy of that rule is a second place for it to rot.
A run that FAILS still answers 201 with status="failed"; only a refusal before the run starts is a 4xx.
Types: WorkflowRunCreateResult
workflows.runs.stream()
POST /v1/workflows/runs — Run a workflow (sync default; mode=async returns job id; mode=stream returns SSE)
Request shape: WorkflowRunCreateParams — what **params accepts beyond the named arguments above.
The same route as create, read as the fourteen-member event union.
mode is forced to "stream" — there is no other value this method makes sense for, and taking it as a parameter would let a caller ask a streaming method for JSON. Forced means forced: a mode= arriving through **params is REFUSED before the request, because **params is spread last and would otherwise win. That is not a hypothetical — the gateway would run and settle the whole workflow synchronously, and this method would then read the JSON body as SSE and raise StreamTruncatedError, telling the caller the connection was cut about a run they paid for in full.
LAZY, like every other stream in this package: nothing is requested until the caller starts iterating, so an unconsumed generator spends nothing. That is the safe direction for a call billed per step.
A completed run under a reused Idempotency-Key replays as a synthetic event sequence; a run still IN FLIGHT under that key is refused with 409 (idempotency_in_progress), which surfaces as a raised error before the first event rather than as an event.
Types: WorkflowRunEvent
workflows.runs.retrieve()
GET /v1/workflows/runs/{run_id} — Get workflow run by id
Types: WorkflowRunResult
workflows.runs.logs()
GET /v1/workflows/runs/{run_id}/logs — Get per-step model_call_logs for a workflow run
Types: WorkflowRunLogsResult
workflows.runs.cancel()
POST /v1/workflows/runs/{run_id}/cancel — Best-effort cancel of a running or queued workflow run
Types: WorkflowRunCancelResult
client.workflows.templates
workflows.templates.list()
GET /v1/workflows/templates — List workflow templates (paginate + filter)
Request shape: WorkflowTemplateListParams — what **params accepts beyond the named arguments above.
Types: WorkflowTemplateListResult
workflows.templates.retrieve()
GET /v1/workflows/templates/{slug} — Get a single workflow template (full, with definition + sample_input)
Types: WorkflowTemplateResult
client.jobs
jobs.retrieve()
GET /v1/images/jobs/{job_id} — Poll an image generation job
Types: JobStatus
jobs.wait()
on_progress is forwarded, not implemented here: poll_job owns the loop, and this stays the thin passthrough it has always been. It is what lets media.wait report progress for every modality by COMPOSING this resource instead of calling the loop behind it.
Types: JobStatus
Types
Every type the signatures above name, plus every request shape they link to, alphabetically. A field marked required must be present; the rest are optional. Notes are the type’s own documentation, so they say what a field means rather than restating its name. AResult is a frozen dataclass and carries the untouched response body on raw, so a field the wire adds tomorrow is reachable today, just not by name. A Params is a TypedDict — documentation and call-site typing only, never enforced at runtime, because every method also takes **params.
AudioTransformationParams
POST /v1/audio/transformations’s body. model required.
AudioTransformationResult
POST /v1/audio/transformations’s 200. No required array declared.
CapabilityRunParams
POST /v1/capabilities/{id}/run’s body. Entirely optional — shape depends on the capability.
CapabilityRunResult
POST /v1/capabilities/{id}/run’s 200 — id, capability, and credits_used are required; the shape of the payload itself varies per capability, so file_id/url/mime/size_bytes/result are all optional.
ChatAnnotation
ChatAnnotationDto. A web-search url_citation annotation — every field optional because the translator fills in only what the upstream provider returned.
ChatCompletionChunk
One data: payload of POST /v1/chat/completions’s SSE response.
The document models a SINGLE payload because OpenAPI 3.0 has no vocabulary for “this schema, repeated, terminated by a literal data: [DONE]” — so text/event-stream’s schema on that operation is ChatCompletionChunkDto, and this type is bound to it.
created and model are absent on the gateway’s own trailing chunk, which is why they are optional here rather than because a provider omits them on a content chunk.
ChatCompletionChunkChoice
ChatCompletionChunkChoiceDto. finish_reason is null on every chunk but the last one for this choice — nullable, so None is a value it takes rather than a key that is missing.
ChatCompletionChunkDelta
ChatCompletionChunkDeltaDto. Every field optional: which ones appear depends on the chunk’s position in the turn and on the provider path.
ChatCompletionParams
POST /v1/chat/completions’s body. model/messages required.
ChatCompletionResult
POST /v1/chat/completions’s 200. The spec’s schema for this operation declares no required array at all, so every named field here is optional — a request that somehow got back an empty {} is still a valid instance.
ChatCompletionTokensDetails
ChatCompletionChunkCompletionTokensDetailsDto. Both optional — no single provider path sets both at once.
ChatPromptTokensDetails
ChatCompletionChunkPromptTokensDetailsDto. Omitted entirely when nothing was cached, so a non-caching stream is byte-wise unchanged from before caching support existed. cache_write_1h_tokens is this gateway’s own extension, carrying Anthropic’s 1-hour TTL cache writes — OpenAI’s shape has no field for it.
ChatToolCallDelta
ChatCompletionChunkToolCallDeltaDto — a tool call as it appears on a STREAMING chunk, which is NOT the completed ChatToolCall shape. id and type arrive only on the chunk that starts the call.
ChatToolCallFunctionDelta
ChatCompletionChunkFunctionCallDeltaDto. arguments is a FRAGMENT of the JSON-encoded arguments string — concatenate across chunks, per tool_calls[].index, before parsing it.
ChatUsage
ChatCompletionChunkUsageDto, with the three base counters OPTIONAL where the DTO types them required.
Not a weakening to make a check pass. The gateway authors its own usage chunk with all three, but a pass-through provider forwards an UPSTREAM usage chunk this gateway never wrote, and nothing guarantees what that one carries. Promising them here would raise a KeyError in caller code on a stream that is perfectly valid. ChatUsage in chat.ts:332-339 makes the same call for the same reason, and types.spec.ts:818-842 binds the three counters individually instead — tests/test_spec_conformance.py excludes exactly these three from the REQUIREDNESS direction and keeps them bound as properties.
ChatWebSearch
ChatWebSearchDto — what the grounded search actually did. Rides the final chunk only, and only when a web-search provider ran.
ChatWebSearchCitation
ChatWebSearchCitationDto. title is an empty string, never absent, when the provider reported none.
EmbeddingParams
POST /v1/embeddings’s body. model/input required.
EmbeddingResult
POST /v1/embeddings’s 200. No required array on the spec side — every field here is optional for the same reason as ChatCompletionResult.
FileContentResult
GET /v1/files/{fileId}/content’s 200 is application/octet-stream bytes — no JSON envelope, so (like SpeechResult) there is no wire schema to bind and no from_body/raw here.
content_type is the response’s actual Content-Type: a download whose type the caller has to guess from the filename is a download this SDK made worse than the wire. The default is the fallback for a response that omits the header.
credits_used reads x-credits-used. A content download is not itself billed today, so it is normally None — carried anyway because the header is the only statement of that and Files.content returning bare bytes made even a non-zero figure unreadable.
FileCreateParams
POST /v1/files’s multipart body. Both parts required — no optional part exists on this route, so this is total=True (the default).
FileDeleteResult
DELETE /v1/files/{fileId}’s 200. No required array declared.
FileListParams
GET /v1/files’s query. Entirely optional.
FileListResult
GET /v1/files’s 200. No required array declared.
FileObject
POST /v1/files’s 201 AND GET /v1/files/{fileId}’s 200 — the same inline schema on both routes. Neither declares a required array, so every field is optional here, id included — a fact worth distrusting in a resource method even though the gateway always sends it in practice; the spec is what this binds to, not what a fixture happens to return.
FilePurpose
The six stored purposes, straight off POST /v1/files’s schema. This is the field TypeScript’s own binding caught typed as a bare string before the fix at sdks/typescript/src/resources/files.ts:4 — kept here as the same six-member literal so a typo in either SDK is a type error, not a silent 400 from the gateway.
ImageEditParams
POST /v1/images/edits’s body. model/prompt/image_base64 required — the OpenAI SDK posts multipart for this call; this endpoint takes JSON instead.
ImageGenerateParams
POST /v1/images/generations’s body. model/prompt required.
ImageResponse
POST /v1/images/generations’s AND POST /v1/images/edits’s 200 — the same schema (ImageGenerationResponseDto) on both routes, whose required names all three fields, so all three are required here.
All three were optional until #817, when the routes still hand-wrote their response schema inline: an inline schema carries no required unless somebody types one, and nobody had — so the SAME payload was published as required on /v1/images/upscale and optional here, and this type encoded that inconsistency rather than the wire.
ImageUpscaleParams
POST /v1/images/upscale’s body. model/image_url required.
ImageUpscaleResult
POST /v1/images/upscale’s 200 — same three properties as ImageResponse, but here the spec’s required array names all three.
JobStatus
The cross-modality media job — served by GET /images/jobs/{id} for EVERY durable media job despite the /images path: image, video (only when deferred), 3D, upscale, speech and transcription alike.
Disjoint from VideoJobStatus: this has no created, no model.
Field defaults mirror the wire exactly, per GET /v1/images/jobs/{id} in apps/docs/openapi.json: only id/status/progress are in that schema’s required, so those three are the only fields without a default — tests/test_spec_conformance.py binds this both ways and would fail with “type requires […], the spec does not” if any of the rest lost theirs.
MediaArtifact
One produced artifact, normalised across modalities.
AT MOST ONE of url, b64 and data is set, and which one is a property of the ENDPOINT rather than of the request:
url— every asynchronous modality, and images unless base64 was asked for.b64— images withresponse_format="b64_json", and music tracks that came back inline.data—audioonly.POST /v1/audio/speechanswers with an audio body, not JSON, so there is nothing to link to. (bytesin the TypeScript SDK; renamed here becausebytesis a builtin.)
__post_init__ refuses TWO, because two could only ever be this SDK’s own mapping bug. Zero is left reachable, because it is what a gateway response with an artifact but no deliverable maps to, and raising there would cost a caller a result they have already been billed for — the same reason MediaResult.artifacts is empty rather than fabricated for a video job with no result yet.
MediaModality
The modalities generate() can produce.
Deliberately the SAME vocabulary the catalogue reports on GET /v1/models as _infery.modality, so a caller can take the value straight off a model they picked and pass it through without a translation table. That is the whole point: the modality becomes DATA rather than a choice of method.
text, stt, vision, embedding and rerank are catalogue modalities too and are absent here on purpose — none of them produces a media artifact. chat, audio.transcriptions and embeddings remain their own calls.
MediaProgress
Progress across modalities.
Both endpoints behind wait report a percentage — progress is a required field on GET /images/jobs/{id} and on GET /videos/generations/{id} alike — so progress is populated for every modality. The claim that only video had one described what this SDK forwarded, not the wire.
status is optional, unlike the TypeScript MediaProgress.status: Python’s Videos.generate calls its on_progress with the SUBMIT result before the first poll, and POST /v1/videos/generations declares no required array, so VideoSubmitResult.status is legitimately absent. Claiming str here would mean inventing a status nobody reported.
MediaResult
What every modality answers with.
The from_* constructors below are the only mapping in this module; the per-modality resources have already turned the wire into their own dataclasses, and these turn those into this one shape.
ModelEstimateParams
POST /v1/models/{slug}/estimate’s body. Entirely optional — the spec declares no required array; which fields matter depends on the model’s modality.
ModelEstimateResult
POST /v1/models/{slug}/estimate’s 200 — a single, nullable credits field (null means “no active price”, not “free”). No required array declared.
ModelListParams
GET /v1/models’s query. Entirely optional.
ModelListResult
GET /v1/models’s 200. No required array declared.
MusicCompletedEvent
The terminal success frame, immediately before data: [DONE].
MusicErrorEvent
The terminal failure frame. Once SSE headers are sent the gateway cannot fall back to an HTTP error, so a failure arrives as a frame and the stream still ends with data: [DONE] — a caller that ignores type sees a successful, empty stream.
MusicGenerateParams
POST /v1/music/generations’s body. model/prompt required; the rest is Suno/Lyria-specific and provider-dependent.
MusicGenerateResult
POST /v1/music/generations’s 200. No required array declared.
MusicProgressEvent
Emitted while the track renders. error here is a provider-reported string on a still-open stream — the terminal failure is MusicErrorEvent.
MusicStreamError
MusicStreamEvent
Discriminated on type, which is required on all three members so a type-checker can narrow event["type"].
MusicProgressEvent · MusicCompletedEvent · MusicErrorEvent
SpeechParams
POST /v1/audio/speech’s body. model/input required.
voice is NOT required and never was enforced as such — the operation declared it required while no handler rejected a request that omitted it, which is #869. Omit it and the routed provider supplies a default.
SpeechResult
POST /v1/audio/speech’s 200 is audio/wav bytes — no JSON envelope at all, so there is no wire schema to bind and no from_body/raw here, and tests/test_spec_conformance.py skips this operation for that reason (BINARY_OPERATIONS).
content_type is the response’s actual Content-Type, for a caller that wants to save the bytes with the right extension; the default is the fallback for a response that omits the header, not a claim about the body.
credits_used is the ONLY place the cost of a speech call is readable — the header on a synchronous answer, and the job’s settled figure when the call deferred. Speech.create returned bare bytes until 0.1.0 and both of those were unreachable: x-credits-used was on a response object the method dropped, and the deferral’s figure was on a JobStatus it discarded after taking the url out of it.
ThreeDGenerateParams
POST /v1/3d/generations’s body. model required.
ThreeDGenerateResult
POST /v1/3d/generations’s 200 — all three properties required, same as ImageUpscaleResult/VideoUpscaleResult.
ToolListResult
GET /v1/tools’s 200 — both object and data are in the spec’s required array, unlike GET /v1/models.
TranscriptionParams
POST /v1/audio/transcriptions’s JSON body variant. model/ file_base64 required.
NOT what audio.transcriptions.create sends. That method posts the multipart form the same route also accepts — file as bytes plus filename — because a caller holding a file should not have to base64 it first. The optional keys below are the ones both variants share and are what **params accepts there; file_base64 is not one of them.
TranscriptionResult
POST /v1/audio/transcriptions’s 200 is a oneOf of two INLINE schemas — one full (credits_used, duration, language, segments, text), one minimal (credits_used, text) for the plain-text response formats.
Rule applied here and in tests/test_spec_conformance.py: the Python type is the UNION of every oneOf member’s properties (so a field only one member has is still reachable), and a field counts as required only if EVERY member requires it. Neither member declares a required array here, so every field below is optional under that rule.
VideoJobStatus
A video generation job — served by GET /videos/generations/{id}.
Disjoint from JobStatus: no data, no payload, no artifacts_expired; carries created/model instead.
Same requiredness rule as JobStatus: apps/docs/openapi.json requires id/status/progress/created/model on this response and nothing else, so those five are the only fields without a default.
VideoSubmitParams
POST /v1/videos/generations’s body. model/prompt required.
VideoSubmitResult
POST /v1/videos/generations’s 200 — the job’s own identity, NOT a completed result. Disjoint from VideoJobStatus: no result, credits_used, or error, ever, per this operation’s schema. No required array declared, so every field is optional.
VideoUpscaleParams
POST /v1/video/upscale’s body. model/video_url required.
VideoUpscaleResult
POST /v1/video/upscale’s 200 — all three properties required.
WorkflowCreateParams
POST /v1/workflows’s body. name/definition required.
WorkflowDeleteResult
DELETE /v1/workflows/{id}’s 200 — both properties required.
WorkflowEstimateParams
POST /v1/workflows/estimate’s body. Entirely optional — either definition (inline) or pipeline_id/pipeline_version (saved) names the workflow to estimate.
WorkflowEstimateResult
POST /v1/workflows/estimate’s 201 — every property required.
WorkflowForeachCompletedEvent
WorkflowForeachStartedEvent
WorkflowIterationFailedEvent
WorkflowIterationStartedEvent
WorkflowIterationSucceededEvent
WorkflowListParams
GET /v1/workflows’s query. Entirely optional. The wire types limit/offset as string (an HTTP query value always is, before parsing) — this is the ergonomic int a caller actually passes.
WorkflowListResult
GET /v1/workflows’s 200 — every property required.
WorkflowResult
GET /v1/workflows/{id}’s 200 — every one of its 15 properties is in the spec’s required array, deletedAt/createdByUserId (nullable) included: the KEY must be present even when the value is null.
WorkflowRetrieveParams
GET /v1/workflows/{id}’s query. Entirely optional.
WorkflowRunCancelResult
POST /v1/workflows/runs/{id}/cancel’s 201 — both properties required.
WorkflowRunCompletedEvent
pipeline.completed — one of the two terminal events, immediately before data: [DONE].
WorkflowRunCreateParams
POST /v1/workflows/runs’s body. input required; exactly one of definition or pipeline_id names the workflow to run.
WorkflowRunCreateResult
POST /v1/workflows/runs’s 201 is a oneOf of the full run result (sync mode) and a queued-run stub carrying only id/status/ createdAt (async mode) — see the module docstring for the union rule. Applied here: properties are the union of both members (11, all from the full-result member since the stub’s 3 are a subset); required is the INTERSECTION, {id, status, createdAt} — the only three the stub also declares required.
Disjoint from WorkflowRunResult even though the field NAMES match: attempt/maxAttempts/creditsUsed/stepRuns/durationMs are required when reading a run back, but NOT on this operation’s own response, precisely because that response might be the async stub.
WorkflowRunError
The failure on pipeline.failed. stepId is the extra field a RUN-level error carries and a step-level one does not: which step took the run down.
WorkflowRunEvent
Discriminated on type, required on all fourteen members so a type-checker can narrow event["type"].
WorkflowRunStartedEvent · WorkflowStepStartedEvent · WorkflowStepDeltaEvent · WorkflowStepCompletedEvent · WorkflowStepFailedEvent · WorkflowStepSkippedEvent · WorkflowRunCompletedEvent · WorkflowRunFailedEvent · WorkflowForeachStartedEvent · WorkflowIterationStartedEvent · WorkflowIterationSucceededEvent · WorkflowIterationFailedEvent · WorkflowForeachCompletedEvent · WorkflowUnknownEvent
WorkflowRunFailedEvent
pipeline.failed — the other terminal event. A run that fails half way has still paid for the steps that ran, so creditsUsed is meaningful here and not zero.
WorkflowRunLogsResult
GET /v1/workflows/runs/{id}/logs’s 200 — data is required.
WorkflowRunResult
GET /v1/workflows/runs/{id}’s 200 — the full run, whatever its mode. id/status/attempt/maxAttempts/creditsUsed/stepRuns/ durationMs/createdAt are required; input/output/error are not (input/output are absent for a run that hasn’t produced them yet, error only for a failed run).
WorkflowRunStartedEvent
pipeline.started — the wire name, not run.started.
WorkflowStepCompletedEvent
WorkflowStepDelta
step.delta’s payload — the gateway’s OWN ChatCompletionChunkDelta (runner/pipeline-event.types.ts:4-15), deliberately NOT this SDK’s ChatCompletionChunk graph above.
The two diverged: PR #780’s chat chunk requires id, object and choices, and each choice requires index and delta — none of which a step-local delta promises, because it comes from inside a pipeline model step rather than from a top-level chat completion. Reusing ChatCompletionChunk here would claim fields a step delta does not carry, and a caller reading chunk["object"] off one would raise KeyError on a perfectly valid frame.
Note also that the gateway’s interface is a WHOLE-CHUNK shape (its own choices, id, usage) while this SDK’s same-named ChatCompletionChunkDelta is a PER-CHOICE delta. Same name upstream, unrelated shape.
WorkflowStepDeltaChoice
WorkflowStepDeltaChoiceDelta
The innermost delta of a model step’s streamed chunk.
WorkflowStepDeltaEvent
step.delta — token-level output of a model step, while it runs.
WorkflowStepDeltaUsage
WorkflowStepError
The failure on step.failed and iteration.failed, and on a step run’s own error. Both keys required — the runner authors this object itself (pipeline-event.types.ts), so neither half is ever missing.
WorkflowStepFailedEvent
WorkflowStepSkippedEvent
step.skipped, and its three reasons are NOT interchangeable.
not_selected is an only_step_id run’s other steps: they did not run at all this time. resumed_from_prior_attempt is a step whose result was carried over from an earlier attempt of this same run — it DID run, just not now. condition_false is the unambiguous third case: the step’s condition evaluated false. Showing one for another tells the caller a step succeeded when nothing touched it.
WorkflowStepStartedEvent
WorkflowStreamStepResult
One entry of a terminal event’s stepRuns — one per step ATTEMPT, not one per step.
childRunIds is OPTIONAL here, unlike on the JSON routes where the gateway’s own comment calls its presence a “stable contract”. That population happens in the JSON-response enrichment layer (pipeline-run-persistence.service.ts, pipeline-runs.service.ts), which the streaming path never runs through — the runner yields r.stepRuns straight into the terminal SSE events, and the gateway’s own StepRunResult.childRunIds is optional for exactly this reason (types/pipeline-run.types.ts:20). Reading len(step["childRunIds"]) off a real stream payload raises KeyError; promising it here would hide that.
WorkflowTemplateListParams
GET /v1/workflows/templates’s query. Entirely optional.
WorkflowTemplateListResult
GET /v1/workflows/templates’s 200 — every property required. Same shape as WorkflowListResult but a DIFFERENT spec component (TemplateListResponseDto vs PipelineListResponseDto), kept as a separate type for that reason.
WorkflowTemplateResult
GET /v1/workflows/templates/{slug}’s 200 — 8 of its 12 properties are required (slug, title, description, category, tags, step_types_used, thumbnail_url, created_at); definition, sample_input, and inputs are absent when unavailable is present, and unavailable itself is absent otherwise.
WorkflowUnknownEvent
The fourteenth member: the escape hatch for an event: name outside the thirteen above.
A gateway that adds a fourteenth real event must not break every existing caller’s loop, so an unrecognised name is yielded as this rather than dropped or raised. name carries the raw event: line — possibly '', for a block that had no event: field at all — and data the raw parsed payload, so a caller who wants to handle the new event can, before this SDK has shipped a type for it.
unknown_event is also the one type literal in this union with NO DOT in it, which is what lets WORKFLOW_RUN_EVENT_NAMES below tell the escape hatch apart from a real gateway event name without a hand-maintained exclusion list. sdks/typescript/src/resources/workflows.ts:634-640 uses the same rule.
WorkflowUpdateParams
PUT /v1/workflows/{id}’s body — a partial update, so the spec declares no required array at all.
WorkflowWriteResult
POST /v1/workflows’s 201 AND PUT /v1/workflows/{id}’s 200 — both resolve to the SAME PipelineWriteResultDto component on the spec side, so one type serves both operations. Every property required.