Skip to main content
Every call both clients make, grouped the way the clients are. Each method shows its real signature for 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()

Produces media of any modality and WAITS for it. Waiting is the default because it is the behaviour that lets one code path serve every modality: images answer in seconds, video takes minutes, and a caller that had to know which is which would be back to branching. Pass 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()

Collects a job started with 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()

Types: VideoSubmitResult · VideoJobStatus

videos.wait()

Collects a video generation job started with 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. A Result 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 with response_format="b64_json", and music tracks that came back inline.
  • dataaudio only. POST /v1/audio/speech answers with an audio body, not JSON, so there is nothing to link to. (bytes in the TypeScript SDK; renamed here because bytes is a builtin.)
“At most”, not “exactly”, is the enforceable half and so it is the half stated: __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"].
Members: 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"].
Members: 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.