---
title: "Generated SDKs"
description: "Generate a native typed client for your API in TypeScript, Python, or Go."
url: https://typeship.dev/docs/targets/sdk
markdown: https://typeship.dev/docs/targets/sdk.md
section: "SDKs"
---

> Requested code examples: python. Shared explanations and shell/configuration examples are retained. Missing examples are marked; examples are never translated. Language guides: [typescript](https://typeship.dev/docs/targets/sdk/typescript.md?codeLanguage=typescript), [python](https://typeship.dev/docs/targets/sdk/python.md?codeLanguage=python), [go](https://typeship.dev/docs/targets/sdk/go.md?codeLanguage=go).

> ## Documentation index
> Fetch the documentation index at https://typeship.dev/llms.txt or every prose page and both generated references at https://typeship.dev/llms-full.txt.
> Append .md to any prose docs URL, or send Accept: text/markdown, for the markdown twin of that page.
> Select existing code examples with Accept-Code-Language: typescript, python, or go (one value), or add ?codeLanguage=python to a Markdown URL. The query parameter takes precedence. Unsupported values return the full docs with a notice.

# Generated SDKs

Generate a native typed client for your API in TypeScript, Python, or Go.

Typeship generates native TypeScript, Python, and Go SDKs for your API. Each package includes typed requests and responses, documented errors, pagination, retries, hooks, and optional runtime validation.

Choose any language your users need. Each SDK is an independent package and release stream; generating a CLI or MCP server never creates a public SDK implicitly.

Use this reference for shared client behavior and select your language in the examples. The language pages cover additional [TypeScript](https://typeship.dev/docs/targets/sdk/typescript), [Python](https://typeship.dev/docs/targets/sdk/python), and [Go](https://typeship.dev/docs/targets/sdk/go) options. To generate and install a package first, follow the [Quickstart](https://typeship.dev/docs/quickstart).

The examples below use a fictional Parcel API and a locally generated `parcel-client` package. They illustrate client behavior; the package and endpoints are not published tutorial services. Use the names and operations in your generated README.

## What ships in the package

**Python**

```text
parcel/
  pyproject.toml        dependencies = [], requires-python >= 3.11
  parcel/__init__.py      ParcelClient, errors, models, webhooks
  parcel/models.py        TypedDicts and Literal enums
  parcel/resources/*.py   one module per resource
  parcel/_core.py         urllib runtime, retries, pagination
  parcel/webhooks.py      when the spec declares webhooks
  parcel/py.typed
  tests/test_client.py    unittest against an http.server stub, standard library only
  api.md
  api.json
  AGENTS.md
  PUBLISHING.md
```

## Forward-compatible response types

Request types stay strict: the SDK should stop a caller from sending a value the Spec does not allow. Response types are more tolerant because servers can add enum values, discriminator variants, and fields before every consumer upgrades.

* When one component is meaningfully different on the wire, generated models split into directional names such as `ShipmentWrite` for requests and `ShipmentRead` for responses. Nested `readOnly`, `writeOnly`, enums, and discriminators participate in that decision; Typeship does not duplicate models whose shapes are actually identical.
* Response enums keep autocomplete for documented values while accepting an unknown string. Request enums remain closed.
* OpenAPI tagged unions include an unknown-object fallback on responses. In TypeScript, comparing the tag to a documented value, such as `if (event.type === "shipment.delivered")`, narrows to that member. A member added later reaches the fallback branch as an `UnknownVariant`; read its tag with `String(event.type)`. GraphQL unions remain closed over the schema revision because `__typename` selection is explicit; the raw GraphQL selection and response remain the escape hatch.
* TypeScript response metadata preserves `rawBody`; Go's `APIResponse.Body` and union `Raw()` preserve bytes. Python response validation accepts future enum and discriminator values instead of rejecting a successful server response.

These rules permit additive server changes while retaining strict request types. Removing a documented response variant or widening a request remains visible in compatibility review.

## Create a client

**Python**

```python
import os
from parcel import ParcelClient

client = ParcelClient(bearer_token=os.environ["PARCEL_TOKEN"])
# or, with PARCEL_TOKEN set in the environment:
client = ParcelClient()
```

### Client options

Every option is optional when the spec pins a server URL. If it does not, `baseUrl` becomes a required argument and generation warns you.

**Python**

```python
client = ParcelClient(
    base_url="https://api.parcel.example/v1",
    bearer_token=token,
    timeout=60.0,
    max_retries=2,
    default_headers={"Request-Source": "billing"},
    transport=my_transport,      # swap urllib for anything with the same signature
    on_request=..., on_response=..., on_error=...,
    debug=False,
    validate=False,
)
```

### Authentication

For shared API settings, OAuth applications, and each Target's responsibilities, see [Authentication](https://typeship.dev/docs/authentication).

Which auth options exist depends on the security schemes in your spec.

| Spec declares                       | TypeScript                           | Python                 | Go                                                                     |
| ----------------------------------- | ------------------------------------ | ---------------------- | ---------------------------------------------------------------------- |
| HTTP bearer, OAuth2, OpenID Connect | `bearerToken`                        | `bearer_token`         | `WithBearerToken`, `WithBearerTokenFunc`, `WithBearerTokenFuncContext` |
| One API key header                  | `apiKey`                             | `api_key`              | `WithAPIKey`                                                           |
| Several API keys                    | one option per header or query param | one keyword per param  | one `With...` per param                                                |
| HTTP basic                          | `basicAuth: { username, password }`  | `username`, `password` | `WithBasicAuth`                                                        |
| OAuth2 client credentials           | `clientCredentials`                  | `client_credentials`   | `WithClientCredentials`                                                |

Bearer tokens and API keys accept a callback, resolved before every attempt. Use it for credentials that expire:

> This example is available in typescript; a python example is not available here.

In Python, the asynchronous client accepts `async def` callbacks as well as synchronous ones; the synchronous client accepts synchronous callbacks only. When a Python request sent with a callback or client-credentials token receives `401`, the client discards the cached token, resolves the credential again, and resends once without using the retry budget. Static credentials are never resent, and an exception raised by a callback propagates unchanged. Python's `client.with_credentials(...)` returns a client that sends only the credentials you pass, sharing the original client's other settings and connections. See [Python](https://typeship.dev/docs/targets/sdk/python#use-other-credentials).

In Go, pass a bearer callback with `WithBearerTokenFunc` and an API-key callback with `WithCredentialFunc(schemeName, callback)`. Their `Context` variants, `WithBearerTokenFuncContext` and `WithCredentialFuncContext`, receive the request's context so a token lookup can honor its deadline and cancellation. A callback error is returned unchanged and is not retried. When a Go request authenticated by a callback or client-credentials token receives `401`, the client discards any cached token and sends the request once more, without using a retry. Static credentials are not resent. See [Go: Choose credentials for each endpoint](https://typeship.dev/docs/targets/sdk/go#choose-credentials-for-each-endpoint).

When the Spec or shared authentication configuration enables OAuth2, `clientCredentials` fetches and caches access tokens for a server application. The client posts to the token URL of the spec's `clientCredentials` flow, or your configured token URL, caches the token until a minute before expiry, and shares one in-flight token request across concurrent calls. An explicit bearer token always wins.

> This example is available in typescript; a python example is not available here.

Python takes `client_credentials={"client_secret": ...}` and Go takes `WithClientCredentials(parcel.ClientCredentials{ClientSecret: ...})` when the selected OAuth application supplies the client ID. Otherwise, provide `client_id` or `ClientID` too. Both cache and coordinate token requests. In Go, the token request uses the calling request's context.

A selected OAuth application supplies the client ID and client authentication method; the shared OAuth server supplies the token endpoint, scopes, audience, and resource. Runtime options take precedence. With these defaults configured, TypeScript only needs `clientCredentials: { clientSecret: process.env.PARCEL_CLIENT_SECRET! }`. Keep this secret in your server application, never browser code or generated configuration. See [Configure a machine client](https://typeship.dev/docs/projects/config#configure-a-machine-client).

A server that calls your API for many users can create one client and derive a copy per user. In TypeScript, `withCredentials` returns a client with the same configuration and only the credentials you pass; nothing is inherited from the original client:

> This example is available in typescript; a python example is not available here.

When the API returns `401` for a request that used a callback or a `clientCredentials` token, the TypeScript client drops the cached token, resolves the credential again, and resends once. A static credential is not resent. An error thrown by your credential callback is thrown unchanged and is not retried.

### Environments

When the spec declares two or more servers, the SDK exports them by name, taken from each server's description. The first server stays the default.

**Python**

```python
from parcel import ParcelClient, ENVIRONMENTS

client = ParcelClient(base_url=ENVIRONMENTS["sandbox"], bearer_token=token)
```

## Call an operation

Operations are grouped into resources by their first tag. Path parameters are positional, in path order. The body comes next, then query and header parameters, then per-call options.

**Python**

```python
client.shipments.get("shp_123")
client.shipments.create(recipient="Ada Lovelace", service="ground")   # body fields become keyword arguments
client.shipments.update("shp_123", service="express")   # named ($ref) bodies too
client.shipments.list(limit=50, created={"gte": 1700000000})
```

### Per-call options

The last argument of every method overrides the client for that one call.

**Python**

```python
client.shipments.get("shp_123", request_options={
    "timeout": 5.0,
    "max_retries": 0,
    "headers": {"Request-Source": "cron"},
})
```

Precedence is the same everywhere: per-call, then per-operation policy set at generation, then the client.

## Results and errors

**Python**

Python raises. Every generated SDK exception derives from `SdkError` and carries `code`, `status`, `body`, and `request_id`:

```python
from parcel import ApiError, NotFoundError, ResponseParseError, SdkError, TransportError

try:
    shipment = client.shipments.get("shp_123")
except NotFoundError as exc:
    print(exc.code, exc.status, exc.body, exc.request_id)
except ApiError as exc:
    ...   # any documented or undocumented status
except ResponseParseError as exc:
    print(exc.status, exc.body, exc.request_id)
except TransportError:
    ...   # no HTTP response
except SdkError:
    ...   # another SDK failure, such as validation
```

Status family classes such as `NotFoundError` and `UnprocessableEntityError` extend `ApiError` and are raised for every response with that status, documented or not. A `304` to a conditional request raises `NotModifiedError`. Any other undocumented status raises `UnexpectedApiError`; malformed JSON in a successful response raises `ResponseParseError`. Return values are the parsed JSON, typed as `TypedDict`s, so you read `shipment["id"]`.

### Failures inside successful responses

Some APIs report a failure in a `2xx` body. The SDK raises these instead of returning them as data:

* A success envelope whose `ok` or `success` flag is `false`, when the spec declares that flag as always `true` (`enum: [true]`) or declares an `error` or `errors` field beside it. Slack's `{"ok": false, "error": "invalid_auth"}` and Cloudflare's `{"success": false, "errors": [...]}` both qualify. Other boolean `success` fields stay ordinary data.
* A GraphQL result that resolves to an error type of a union or interface, such as `UserError` or `AccessDeniedError`. Members whose names end in `Error` count, as long as the union also has a non-error member. The SDK always selects `__typename` on these fields so it can tell.

These raise `PayloadError` (`*PayloadError` in Go), an `ApiError` with the HTTP status and the failure body. Its `code` comes from the body (`invalid_auth`, `10000`), and GraphQL failures carry the error type in `typename` (`Typename` in Go). The check runs before [runtime validation](#runtime-validation), so a failure body is reported as the failure rather than as a schema mismatch.

### Rate limits

A rate-limited call raises `RateLimitError`, whether it arrives as a `429` or as a `403` whose headers say the limit is spent. Every API error carries `rateLimit` (`rate_limit` in Python, `RateLimit` in Go) when the response was a rate limit, with `retryAt` set to when the limit resets if the API said. The message names that time, for example `Rate limited: wait until 2026-09-26T16:04:12Z, then retry.` The generated CLI and MCP server report these as `RATE_LIMITED` with the same time.

### Conditional requests

Send `If-None-Match` or `If-Modified-Since` with a per-call header to ask whether a resource changed since your last read. When it has not, the API answers `304 Not Modified` with no body, and the SDK raises `NotModifiedError` (`*NotModifiedError` in Go). A `304` arrives only when you send one of these headers, so methods keep their plain return type: `Promise<Shipment>` in TypeScript, `Shipment` in Python, and `*Shipment` in Go, never `null`, `None`, or a nil value with a nil error. A response body that the spec declares nullable stays nullable.

`NotModifiedError` is an `ApiError` with `status` 304 and `code` `not_modified`. It carries the validators to send next time: `etag` and `lastModified` in TypeScript, `etag`, `last_modified`, and `headers` in Python, and `ETag()`, `LastModified()`, and `Header` in Go. Catch it to keep using the copy you already have:

**Python**

```python
from parcel import NotModifiedError

seen = []
shipment = client.shipments.get("shp_123", request_options={"on_response": seen.append})
try:
    shipment = client.shipments.get("shp_123", request_options={"headers": {"If-None-Match": seen[-1].etag}})
except NotModifiedError:
    pass  # unchanged since the first call; keep using shipment
```

`on_response` receives a `ResponseMeta` with `status`, `headers`, `request_id`, `etag`, `last_modified`, and `not_modified`.

List methods behave the same way: a `304` raises instead of yielding an empty page. The generated CLI and MCP tools report a `304` as a successful result, `{"ok": true, "not_modified": true, "etag": "..."}`, with `etag` present when the API sent one.

## Pagination

List operations with a recognized pagination shape iterate every item across every page, fetching lazily.

**Python**

Paginated methods return a generator. A `_page` sibling returns one raw page:

```python
for shipment in client.shipments.list(limit=50):
    print(shipment["id"])

page = client.shipments.list_page(limit=50)   # {"data": [...], "next_cursor": ...}
```

These pagination styles are detected from the spec, in this order of preference:

| Style                 | Detected from                                                                                                                                                                                                                                                                  |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Next URL              | A string field in the response that holds the next page's URL (`next_page_uri`, `next_url`, or `next` described or formatted as a URL), at the top level or inside a container such as `links`. The SDK requests that URL as given.                                            |
| Link header           | A declared `Link` response header, on a bare array response or an envelope. The SDK follows the `rel="next"` URL as given.                                                                                                                                                     |
| `cursor`              | A cursor query param (`cursor`, `page_token`, `after`, `starting_after`, ...) plus a next-cursor field in the response (`next_cursor`, `next_page_token`, ...), at the top level or one level down (`meta.next_cursor`, `response_metadata.next_cursor`, `result_info.cursor`) |
| `cursor_from_last_id` | A cursor param plus a `has_more` field, where items have an `id`. The next request cursors from the last item's id.                                                                                                                                                            |
| `page`                | A `page` or `page_number` query param. Advances while pages look full. A page param with `minimum: 0` starts at page 0.                                                                                                                                                        |
| `offset`              | An `offset`, `skip`, or `start` query param. Advances by the number of items received.                                                                                                                                                                                         |

Response envelopes built with `allOf` are merged before detection, so a shared envelope plus a `result` array (Cloudflare) is recognized, as is an item array one level inside `result` or `data`. A response whose only page of results sits under a named key (`{"categories": {"items": [...], "next": "..."}}`) pages through that key. A response with several such envelopes (a search returning `tracks` and `artists`) is not paginated automatically; generation warns and names them, and a [pagination rule](https://typeship.dev/docs/projects/config#pagination-rules) with an items field such as `tracks.items` picks one. A lone array next to another object, such as a lookup that returns a `file` and its `comments`, is not treated as a page of results.

Iteration stops when the API signals it: `has_more: false`, an empty next cursor or next URL, no `rel="next"` link, a `total_count` (or `total`) reached, a `total_pages` reached, a page shorter than the requested limit, or an empty page.

For these URL styles, `nextPageParams()` returns `{ page_url }`, the next page's URL as the API named it. Pass it back as the `pageUrl` request option (TypeScript) to start a list from that page. The generated CLI's `--page-url` flag and MCP's `page_url` argument do the same.

A next page whose URL is on another origin is not requested, because the request carries your credentials; iteration fails with `PaginationError`, whose code is `pagination_error` in TypeScript, Python, and Go. A page that is missing its items field also fails with `PaginationError` instead of ending the walk as if the list were empty.

If detection does not match your API, a Project's [configuration](https://typeship.dev/docs/projects/config) can pin the style, items field, and cursor fields per operation, or turn pagination off for one. Malformed rules fall back to detection with a warning on the generation.

## Retries and timeouts

* Requests with idempotent verbs (GET, HEAD, PUT, DELETE, OPTIONS) retry on `408`, `429`, `500`, `502`, `503`, and `504`, and on transport failures.
* `429` is retried for every verb, including POST.
* A `403` that says the rate limit is spent (`x-ratelimit-remaining: 0`) or asks you to wait (`Retry-After`) is a rate limit, not a permission error. It is retried like other retryable statuses.
* Backoff is exponential with full jitter, starting at 300ms and capped at 10 seconds per wait. When the response says how long to wait, through `Retry-After` (seconds or HTTP date) or `x-ratelimit-reset` (epoch seconds or seconds until reset), the SDK waits exactly that long. A requested wait longer than 60 seconds is not slept through: the call fails at once with the reset time. Change the ceiling with `maxRetryWaitMs` (TypeScript), `max_retry_wait` in seconds (Python), or `WithMaxRetryWait` (Go).
* Defaults are two retries after the first attempt and a 60 second timeout per attempt. Override the timeout on the client or on a single call.

The policy is tunable per project in [config](https://typeship.dev/docs/projects/config): replace the retryable status set, change the retry count and backoff window, allow retries on non-idempotent operations, or disable retries globally or per operation.

### Idempotency keys

When an operation declares an `Idempotency-Key` header, the SDK generates a UUID for it on every call you do not supply one for, and reuses that UUID across retries of the call. That is what makes retried writes safe. Pass your own through the params object (`idempotencyKey` in TypeScript, `idempotency_key` in Python, `IdempotencyKey` in Go) to control it.

## Hooks and debug logging

Two hooks run on every attempt. `onRequest` runs after auth and body headers are assembled and may mutate `headers` or `url`. Python's `on_request(method, url, headers)` can change headers but not the URL. `onResponse` runs after every HTTP response, before parsing and retry decisions. `onError` runs once per failed call, after retries, with the typed error and the request method and path.

> This example is available in typescript; a python example is not available here.

`debug: true` (or `PARCEL_DEBUG=1` in the environment) logs one line per attempt to stderr: `parcel POST /shipments -> 201 (43ms) req_8f2k1`. Pass a function instead to feed your own logger a structured event with `method`, `path`, `status`, `durationMs`, `attempt`, `requestId`, and the transport error message when there was no response.

Debug events never include headers or bodies, so credentials cannot reach logs through them. Use the hooks when you need request-level detail.

Every generated SDK request carries a `User-Agent` such as `parcel/2.3.0`, identifying the package and version without a generator marker. Your API can use it to distinguish SDK versions. Override the header with TypeScript `defaultHeaders`, Python `default_headers`, or Go `WithRequestHeader` for a call. The packages also expose their version.

## Runtime validation

Types catch mistakes at compile time. They cannot see an API that drifted from its spec at runtime. Turn on validation to schema-check JSON request and response bodies against your spec's own schemas, still with zero dependencies. The validator ships in the package, and the schema table is plain data generated from the spec.

**Python**

```python
client = ParcelClient(validate=True)      # raises ValidationError
client = ParcelClient(validate="warn")    # warnings.warn and proceed
```

`ValidationError.violations` is a list of `(path, message)` pairs. `direction` is `"request"` or `"response"`.

Bad request bodies are caught before any HTTP is sent. The checks honor `readOnly`, `writeOnly`, and nullability. Constraints outside the emitted subset (`format`, `multipleOf`, and similar) are ignored, so validation can miss drift but never rejects valid traffic. All three languages validate against identical tables. GraphQL and streaming operations are not validated.

Generation warns about encountered unsupported constraints such as `unevaluatedProperties`, `multipleOf`, and conditional schemas. Add application checks for those constraints and for business rules expressed only in descriptions. Generated CLIs also validate [path, query, and header parameters](https://typeship.dev/docs/targets/cli#runtime-validation) when `--validate` is enabled.

## Webhooks

When your spec declares a `webhooks` section (OpenAPI 3.1, or `x-webhooks` on 3.0), the package ships typed events and a verifying parser. Verification follows the Standard Webhooks convention: `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers, HMAC-SHA256, a five minute tolerance, and a constant-time compare.

> This example is available in typescript; a python example is not available here.

`unwrap` throws `WebhookVerificationError` on any mismatch. `unwrapUnsafe` parses without verifying. See [Webhooks](https://typeship.dev/docs/guides/webhooks) for the end-to-end flow, including local testing with your CLI.

## Streaming

Operations whose success response is `text/event-stream` return a stream of events instead of a parsed body. Each event has `data`, plus `event` and `id` when the server sends them.

**Python**

```python
for event in client.events.stream():
    print(event.get("event"), event["data"])
```

The per-attempt timeout guards the connection and headers only, so long streams are not cut off. Once events are flowing nothing is retried, because replaying a dropped connection would redeliver events you already handled. A failure to connect surfaces before the first event.

### Operations that stream on request

Some operations answer JSON by default and `text/event-stream` when the request asks for it, through a boolean `stream` field or a `stream_format` field that accepts `sse`. These operations, including `multipart/form-data` uploads such as transcriptions, get a second method whose name ends in `Stream` (`_stream` in Python). It sends the field for you (as a form part for a multipart upload), so its body type leaves the field out, and it yields each event's data parsed into the event schema from the spec. A `data: [DONE]` line ends the stream. An error event (`event: error`, or data with an `error.message`) ends it with an error whose `code` is the API's error code.

**Python**

```python
for chunk in client.chat.create_completion_stream(model=model, messages=messages):
    print(chunk["choices"][0]["delta"].get("content", ""), end="")
```

The JSON method keeps its JSON return type. If you set the stream field on it anyway, the call fails with `unexpected_stream` instead of returning event text as if it were JSON.

The generated CLI also exposes streaming operations and writes one JSON value per event as NDJSON. MCP servers omit them from the tool surface.

## Global parameters

APIs where every call carries the same tenant or version parameter should not make callers repeat it. Naming those parameters in a project's [config](https://typeship.dev/docs/projects/config) promotes them to client options: set once, applied to every operation that accepts them, with per-call values winning.

> This example is available in typescript; a python example is not available here.

The generated CLI and MCP server read the same values from `PARCEL_ACCOUNT_ID`-style environment variables. Query and header parameters are supported. Path parameters are not.

## Types

### Enums

Named string enums generate a runtime value and a type with the same name, so values exist at runtime and the type stays a literal union:

> This example is available in typescript; a python example is not available here.

Python emits `Literal["pending", "in_transit", "delivered"]`. Go emits a defined type with `ShipmentStatusPending`-style constants.

An inline enum, written into a property or a parameter rather than declared as its own schema, stays a type-only literal union in TypeScript and a `Literal[...]` in Python. Go has neither, so it gets a defined type as well, named after the field it belongs to (`SourceKind`, `GenerationMetaBaseline`). When the same set of values turns up in more than one place, it is a concept the API repeats rather than a detail of one field, so it takes the concept's own name and every site shares the one type:

> This example is available in go; a python example is not available here.

A defined string type marshals as the string it is, so nothing about the request or response bytes changes, and a value the SDK predates still decodes rather than failing the call.

### readOnly and writeOnly

Properties marked `readOnly` are omitted from request types, so the compiler stops you sending `id` or `created_at` fields the server owns. `writeOnly` properties are omitted from response types. In TypeScript a referenced schema appears as `Omit<Shipment, "id" | "tracking_number">` in request positions. Python and Go materialize a named variant only when the filter removes something: `ShipmentWrite` in Python, `ShipmentParams` in Go. The canonical type keeps every property.

### Union types

`oneOf` and `anyOf` become TypeScript unions and Python `Union[...]`. Go has no sum types, so they become a named union type holding the raw JSON with `As<Variant>()`/`From<Variant>()` accessors and a `Discriminator()`, never a struct that would silently drop fields. See [Go: Unions](https://typeship.dev/docs/targets/sdk/go#unions).

### Deep bracket encoding

Query objects and form-encoded bodies use bracket-style deep encoding:

```text
{ created: { gte: 5 } }         ->  created[gte]=5
{ items: [{ id: "x" }] }        ->  items[0][id]=x
{ expand: ["a", "b"] }          ->  expand=a&expand=b   (top-level arrays repeat the key)
new Date(...)                   ->  ISO 8601 string
```

JSON bodies are sent as JSON. This encoding applies to query strings and `application/x-www-form-urlencoded` bodies. A query array declared with `explode: false`, `spaceDelimited`, or `pipeDelimited` is joined into one value instead, such as `expand=a,b`. See [Query parameters](https://typeship.dev/docs/reference/spec-compatibility#query-parameters).

Form bodies are identical byte for byte across the three SDKs: pairs are ordered by key, and only letters, digits, `-`, `_`, `.`, and `~` stay unescaped (a space is `+`). A form field whose `encoding` says `explode: false`, `spaceDelimited`, or `pipeDelimited` is joined into one value the same way a query array is.

When a request body offers several media types, the SDK sends one: `multipart/form-data` when it carries files, else JSON, else form fields, else the first listed. Generation warns about every operation whose other variants were dropped.

## GraphQL

Clients generated from a GraphQL schema put queries on `client.query` and mutations on `client.mutation` (`client.Query` / `client.Mutation` in Go). Generated methods select all scalar and enum fields to depth 2 by default, with a fragment per concrete type for unions and interfaces, and every object-returning method takes an optional selection that replaces the default: a typed `select` object in TypeScript that narrows the result type, `select=` in Python, `WithSelection(...)` in Go. The endpoint and authentication scheme come from the [GraphQL Spec settings](https://typeship.dev/docs/projects/config#graphql-specs), since SDL cannot declare them. Relay-style connections auto-paginate. Root fields whose names start with an underscore, such as `_dummy` or `_service`, are placeholder or gateway fields and are not generated. In-band errors surface as `GraphQLRequestError` (`*GraphQLError` in Go). See [Generate from GraphQL](https://typeship.dev/docs/guides/graphql).

## Naming rules

* Operations group into resources by their first tag. Untagged operations group by the first meaningful path segment after a versioned prefix such as `/platform/v1`; `/api` and `/rest` are also skipped. A resource before a path parameter, such as `shipments` in `/shipments/{id}/v1/events`, remains the owner.
* A meaningful `operationId` becomes the method name. Framework suffixes such as `UsingGET` and controller namespaces such as `ShipmentsController.` or `ShipmentsController_` are stripped. Other PascalCase namespaces are stripped only when they end in a resource or route prefix and introduce an action verb.
* A controller's `findAll` or `findOne` becomes `list` or `get` when the GET route agrees. Custom actions remain intact. A controller whose resource differs from an authored tag keeps its namespace, even when it is the tag's only operation. Other ambiguous shortened names also retain their namespace.
* An `operationId` that merely restates method and path (`GetShipmentsShipment`) is treated as absent, and a name is derived by verb and path shape: `list`, `get`, `create`, `update`, `delete`. `GET /shipments` becomes `client.shipments.list()`. `GET /shipments/{id}` becomes `client.shipments.get(id)`.
* An `operationId` that restates a path outside its tag's own resource is kept: `getQuotes` on `GET /quotes` under the `shipments` tag stays `client.shipments.getQuotes()` instead of becoming a bare `list`.
* Well-known trailing action segments win over the generic verb: `POST /shipments/{id}/cancel` becomes `client.shipments.cancel(id)`.
* When a tag names only the last words of the noun every operation under it spells out, and the extra words are path segments, the resource takes the whole noun: `createInternationalLabel` and `listInternationalLabels` on `/international/labels` under a `Labels` tag become `internationalLabels.create` and `internationalLabels.list`. When every operation under a tag acts on the same noun from its path, the noun joins the resource: `createTrackingEvent` and `listTrackingEvents` on `/tracking/events` under a `Tracking` tag become `trackingEvents.create` and `trackingEvents.list`.
* An `operationId` prefixed `beta_`, `alpha_`, or `experimental_` groups into its own resource: `beta_createShipment` under `shipments` becomes `betaShipments.create`.
* Resource words are trimmed from method names when the short form is unambiguous: `shipments.createShipment` becomes `shipments.create`. A method name that only repeats its resource takes the verb for its HTTP method: `GET /search` with `operationId: search` becomes `search.list`.
* Schema names that would shadow a language global or a runtime export get a `Model` suffix: a schema named `Error` becomes `ErrorModel`.
* Go identifiers follow Go's initialism convention: `UserID`, `HTTPStatus`.

These rules also determine generated CLI commands and MCP tool names. Regeneration can rename existing methods, commands, and tools when their source uses framework namespaces or versioned prefixes. Check the generated reference and update callers for those renames; command-shortening aliases do not preserve names from an earlier Generation. See [compatibility review](https://typeship.dev/docs/projects/regeneration#breaking-changes).

## Sitemap

[Documentation index](https://typeship.dev/llms.txt)
