CLI, MCP & SDKsSDKs

Python

Generate synchronous and asynchronous Python clients for your API with typed models and errors.

Typeship generates synchronous and asynchronous Python clients for your API as one focused package with no runtime dependencies. This page covers Python-specific behavior; shared client behavior is on the SDK overview.

CLI and MCP packages are generated and released separately.

Package

  • The distribution and import name derive from your API's title. The Acme API produces acme, installed with pip install . from the package directory or from PyPI once you publish it. Set a different name per project in the console under package names.
  • pyproject.toml declares dependencies = [] and requires-python >= 3.11. The runtime is urllib only.
  • The package ships py.typed, so type checkers see every TypedDict and Literal.

Idioms

  • Raise, don't return. Every failure is an exception under TypeshipError. See Results and errors.
  • Keyword arguments for bodies. When an operation's request body is an inline object (JSON or form-encoded), its fields become keyword arguments: client.accounts.create(name="Ops", currency="usd"). When the body is a named schema, an array, or plain text, it stays one body= argument typed accordingly (AccountWrite, List[AccountWrite], str). If a body field is spelled like a path or query parameter, the whole body steps aside into body= rather than producing a duplicate argument.
  • Dicts, typed. Responses are the parsed JSON, typed as TypedDicts. You read account["id"], not account.id. There is no conversion layer between you and the wire.
  • Directional models stay honest. A component whose request and response contracts differ becomes AccountWrite and AccountRead; identical contracts keep the clean Account name. Response enums accept future strings and discriminator responses retain an unknown Dict[str, Any] fallback, while request types remain closed.
  • Generators for pages. for account in client.accounts.list(): walks every page. client.accounts.list_page() returns one raw envelope.
  • request_options per call. A dict with timeout, max_retries, and headers.

Environment variables

The client reads these when the matching option is not passed:

VariableSets
ACME_TOKENbearer_token
ACME_BASE_URLbase_url
ACME_WEBHOOK_KEYwebhook_key
ACME_DEBUG=1debug

A single API key is api_key= and ACME_API_KEY, the same spelling as the TypeScript SDK and the CLI; several keys get one keyword and variable each, named after the header or parameter. Basic auth reads ACME_USERNAME and ACME_PASSWORD. The prefix comes from your package name.

Transport

Pass transport= to replace urllib. A transport is a callable that takes (method, url, headers, body_bytes, timeout) and returns (status, headers, body_bytes). Use it to route through a proxy, a recording layer, or a test double. Streaming endpoints read the response incrementally and go straight to urllib, so they bypass a custom transport.

Webhooks

client.webhooks.unwrap(payload, headers) verifies and parses. unwrap_unsafe parses only. Module-level verify_webhook and sign_webhook are exported for handlers that do not hold a client. Events are TypedDicts discriminated by their type field. WEBHOOK_NAMES and WEBHOOK_SAMPLES list every declared event with a sample payload.

Async

AsyncAcmeClient takes the same options and has the same methods, awaitable: await client.accounts.get(...), and async for over paginated methods and streams. Requests run on the event loop's default executor, so nothing blocks the loop and there is still nothing to install. Both clients are context managers (with / async with) and have close() / aclose().

The zero-dependency tradeoff is explicit: cancelling a coroutine stops waiting for its result, but it cannot interrupt a synchronous socket call already running in the executor. timeout bounds each socket attempt; it is not one wall-clock deadline across retries. The generated async client is therefore not the right transport when immediate in-flight socket cancellation is a hard requirement.

Uploads

A multipart/form-data field is a keyword argument that accepts raw bytes, an open binary file (its name becomes the filename), or a (filename, data, content_type) tuple; the other fields of the body travel alongside as form parts. A raw binary body (application/octet-stream, image/png, ...) is body= with bytes or a file object, sent under the spec's content type.

GraphQL

A GraphQL schema generates one method per query and mutation field, keyed under client.query and client.mutation. Arguments are keyword arguments and become variables. Every scalar field to depth 2 is selected by default, with a fragment per concrete type for unions and interfaces, and select="{ id name }" overrides that with a raw selection set on methods that return an object. Connection fields paginate like everything else. Errors in a 200 raise GraphQLRequestError (an ApiError) with the raw errors list.

Connections

The default transport pools keep-alive connections per client on http.client, one per scheme/host/port, honoring HTTP_PROXY/HTTPS_PROXY/NO_PROXY. A request that fails on a reused connection before any response arrived is retried once on a fresh one (the server closed an idle keep-alive, the same case Go's net/http retries), so pooling never turns into spurious transport errors. Redirects follow the rules urllib applied.

On this page