Both clients are generated from the backend’s own OpenAPI schema, so they cannot drift from the routes. A method exists here when, and only when, the route behind it carries an sdk_expose decorator — and CI regenerates them and fails on a non-empty diff, which is what keeps that true. That also means the SDKs stop where the schema does. Read What the SDKs do not cover before you go looking for a realtime method.

Install

Neither package is on PyPI or npm yet. Install from a checkout of the repository until they are.
The TypeScript package ships dist/, so it has to be built before it is installed.

The client

base_url is the origin without /v1 — the client appends it. Both arguments fall back to EESI_API_URL and EESI_API_KEY, and the URL defaults to https://api.eesi.ai. An unauthenticated client is legal and 401s on almost everything.
list_speech_models() returns .data, not .models. Every list response on these clients is .data.

Making a call

A request body is either its typed model or a plain mapping of the same fields. A mapping is validated against that model before anything is sent, so a misspelled field raises InvalidRequestError naming it rather than failing on the wire.
Import request models from eesi_sdk.models in Python — that module is the public home for them, so regenerating never moves your import. In TypeScript, declare a body away from its call site as the method’s own …Body type (CreateSpeechBody), not as the response model. The response model types the fields the server defaults — response_format, speed — as always present, which is true coming back and wrong going out.

What the SDKs cover

Uploads and audio responses are handled for you: create_speech returns the audio bytes, and create_voice and create_transcription take a file. In Python a file is raw bytes, a binary stream, or a (filename, content) / (filename, content, content_type) tuple — pass the tuple form when the server keys off the extension or the media type, which it does for voice references. In TypeScript it is a Blob.

What the SDKs do not cover

This is the part that saves a support ticket.
/v1/realtime is a WebSocket, so it is not an OpenAPI operation and cannot be generated. /v1/story/*, /v1/speech/sessions/* and /v1/user/memory/* are ordinary REST routes that carry no sdk_expose decorator today. Call all of them with raw HTTP or a socket — do not go looking for a method name that does not exist.
SSE transcription (stream=true) and streamed chat completions come back as a stream the generated client does not decode. Use an OpenAI client against the base URL, or read the response yourself. See OpenAI compatibility.
packages/realtime holds the shared TypeScript decoder for the realtime event stream — alias folding, the cumulative-user-delta rule, assistant segment merging — and every EESI client uses it. It is marked "private": true and is consumed source-direct inside this repository, so it is not something to add to your package.json. The rules it implements are documented in Realtime events; the conformance corpus that pins them is spec/realtime/v1/.
Creating a key and un-archiving one refuse an API key, so neither client has a method for them. See Authentication.

Timeouts and retries

Identical in both clients — change one and you change both. A POST, PUT, PATCH or DELETE that failed on the wire may still have reached the server, and a read timeout means the server took the request — so a retry there is a second voice cloned or a second charge, not a second attempt. Retry those yourself, when you know the operation is safe to repeat. Synthesis of a long input can outrun the 60-second read timeout. Raise it for that call, or stream the response yourself — see Stream speech to a file.

Errors

Everything raised subclasses EESISdkError, so you can catch the categories separately or together. ApiError reads both envelopes the backend uses — FastAPI’s {"detail": …} from the control-plane routes and OpenAI’s {"error": {…}} from the speech routes — so message is always the server’s own sentence. code and type are set only when the envelope carries them.
The field is status_code in Python and statusCode in TypeScript. Both also carry body — the parsed JSON, or the raw text of a non-JSON response.
Every status and code you will meet is in Errors.

Regenerating

After changing a route or an sdk_expose decorator:
That rebuilds the OpenAPI slice, the request and response models, the client mixins and the reference spec in these docs. CI runs it and fails on a non-empty diff.

Next

Quickstart

A key, a synthesized line, and a live conversation.

OpenAI compatibility

Point an existing OpenAI client here instead.

Errors

The codes behind every ApiError.

Cookbook

Working recipes, end to end.