---
title: "Python"
description: "The generated Python SDK: a typed, zero-dependency client built on the standard library, written the way Python developers expect."
url: https://typeship.dev/docs/platforms/sdk/python
markdown: https://typeship.dev/docs/platforms/sdk/python.md
section: "Get started"
---
> ## Documentation index
> Fetch the complete documentation index at https://typeship.dev/llms.txt (every page, one line each) or the full text at https://typeship.dev/llms-full.txt.
> Append .md to any docs URL, or send Accept: text/markdown, for the markdown twin of that page.

# Python

The generated Python SDK: a typed, zero-dependency client built on the standard library, written the way Python developers expect.

The Python SDK shares the [SDK](https://typeship.dev/docs/platforms/sdk) feature set: typed payloads and errors, auto-pagination, retries, hooks, validation, webhooks, and streaming. This page covers what is specific to the Python package. Python packages are SDK-only. The CLI and MCP server are TypeScript platforms.

## 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](https://typeship.dev/docs/guides/publish). 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](https://typeship.dev/docs/platforms/sdk#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 `TypedDict`s. You read `account["id"]`, not `account.id`. There is no conversion layer between you and the wire.
* **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:

| Variable           | Sets           |
| ------------------ | -------------- |
| `ACME_TOKEN`       | `bearer_token` |
| `ACME_BASE_URL`    | `base_url`     |
| `ACME_WEBHOOK_KEY` | `webhook_key`  |
| `ACME_DEBUG=1`     | `debug`        |

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 `TypedDict`s 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()`.

## 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.

## Sitemap

[Every page of these docs](https://typeship.dev/llms.txt)
