Configure your SDKs
Stainless automatically generates idiomatic yet flexible SDKs for your API. But every API is different. Create the perfect SDK for your API and your users by configuring the Stainless Generator.
When you first generate an SDK with Stainless, we create a reasonable draft configuration for you. You can see and edit it in the SDK Studio.
Core Concepts
Section titled “Core Concepts”You configure the code structure and names in your SDK by defining methods, models, and resources in the resources section of your Stainless config.
- A method is a function that makes an API request.
- A model represents a type that can be reused throughout your SDKs.
- A resource is a collection of functions and methods, similar to a namespace, which translates to a class in the SDKs for most languages. Each top-level resource is accessible from the SDK client as a field or method.
- A resource can also contain nested subresources with their own methods, models, and subresources. Each subresource is accessible from its parent resource as a field or method.
Here’s a simple SDK usage example for a todo list API. It’s annotated to show how methods, models, and resources from your Stainless config correspond to entities in your SDK. (We’ll look at the config itself next.)
import { Task, TodoNinja } from "todo-ninja";
const client = new TodoNinja()
// from `tasks` resource ┐// │// Vconst item: Task = client.tasks.create({// ^ ^// | |// | └ from `create` method// |// └ from `task` model name: "Grocery shopping", deadline: "2025-05-01"});
// from `tags` subresource of `tasks`// │// Vclient.tasks.tags.list(task_id)from todo_ninja import TodoNinja, Task
client = TodoNinja()
# from `tasks` resource# │ ┌ from `create` method# | |# V Vitem: Task = client.tasks.create(# ^# |# └ from `task` model name="Grocery shopping", date=date.fromisoformat("2025-05-01"),)
# from `tags` subresource of `tasks`# │# Vclient.tasks.tags.list(task_id)client := todoninja.NewClient()
var task* todoninja.Task // from `task` modelvar err error// from `tasks` resource// |// Vtask, err = client.Tasks.New(context.TODO(), todoninja.TaskNewParams{// ^// └ from `create` method (more on method names below) Deadline: todoninja.Time(time.Now()), Name: "Grocery shopping",})
// from `tags` subresource of `tasks`// |// Vclient.Tasks.Tags.List(context.TODO(), task_id)Now let’s see how the tasks resource, create method, and task model are defined in the Stainless config.
We’ll just look at the resources section of the config for now:
# Stainless configorganization: ...resources: tasks: models: task: "#/components/schemas/Task" # Points to OpenAPI spec methods: create: post /v1/tasks ... subresources: tags: methods: list: get /v1/tasks/{id}/tags...There are three things to notice here:
-
Names in your SDK are derived from the names in your Stainless config. The generator transforms these names to match each language’s naming convention. For example, the
taskmodel becomes aTasktype in Python and TypeScript. Similarly, ablog_postmodel would be renamedBlogPostin those languages.Method names in Go
The Go SDK generator goes one step further. To follow Go’s naming conventions, it translates the methods
createandretrieveto the function namesNewandGet, respectively. -
Methods are mapped to API calls, so the
createfunction in the examples above will send aPOSTrequest to/v1/tasks. -
Models in the Stainless config are mapped to schemas defined in the OpenAPI spec. In this example, the
taskmodel in the Stainless config points to theTaskschema, which defines thenameanddeadlineproperties in the SDK:
# OpenAPI Speccomponents: schemas: Task: type: object properties: name: type: string deadline: type: string format: dateResources
Section titled “Resources”A resource is a group of logically related methods, models, and subresources. Resources in your SDK typically correspond to REST resources in your API, but you can reorganize them depending on their intended usage. Method names do not have to be unique across resources because they function like namespaces.
Consider the following:
resources: accounts: models: account: '#/components/schemas/Account' methods: list: get /accounts create: post /accounts retrieve: get /accounts/{account_id} delete: delete /accounts/{account_id} update: put /accounts/{account_id} subresources: friends: models: friends: '#/components/schemas/Friends' methods: list: get /accounts/{account_id}/friends create: post /accounts/{account_id}/friends delete: delete /accounts/{account_id}/friends/{friend_id}In this example, we have an accounts resource and an accounts.friends resource nested within. The resulting SDK usage is:
account = client.accounts.create(...)friends = client.accounts.friends.list(...)Depending on the constraints and how you want your users to reason about your API, you may want to restructure the resources to look like:
resources: accounts: models: account: '#/components/schemas/Account' friends: '#/components/schemas/Friends' # Moved here methods: list: get /accounts create: post /accounts retrieve: get /accounts/{account_id} delete: delete /accounts/{account_id} update: put /accounts/{account_id} # The items below were moved here and renamed: list_friends: get /accounts/{account_id}/friends add_friend: post /accounts/{account_id}/friends remove_friend: delete /accounts/{account_id}/friends/{friend_id}That resulting SDK usage is:
account = client.accounts.create(...)friends = client.accounts.list_friends(...)Ultimately, you have the final say in how you want to group together the methods in your API. In general, a good approach is to have a resource for each conceptually distinct representational object in your API.
You can also put methods directly on the client by placing them under the special resource $client.
For example, if your resources configuration looks like this:
resources: $client: methods: health: get /healthThen the SDK usage would be:
health = client.health()Models
Section titled “Models”A model maps a schema in your OpenAPI spec to a type in your SDK language. If you want a schema to be represented by the same type throughout your SDK, you should define a model for it.
For example, consider this snippet of a Stainless config:
# Stainless configresources: accounts: methods: list: get /accounts create: post /accounts retrieve: get /accounts/{account_id}If you don’t specify any models, then the Stainless generator will generate methods with the following signatures,
even if they all reference the same #/components/schemas/Account schema in their responses. (Note the distinct return types.)
class Accounts extends APIResource { list(...): AccountListResponse[]; create(...): AccountCreateResponse; retrieve(...): AccountRetrieveResponse;}
// usage:const account: AccountRetrieveResponse = client.accounts.retrieve()Let’s see what happens when we define an account model in the accounts resource:
resources: accounts: models: account: '#/components/schemas/Account' methods: list: get /accounts create: post /accounts retrieve: get /accounts/{account_id}Now the Stainless generator will create these methods:
class Accounts extends APIResource { list(...): Account[]; create(...): Account; retrieve(...): Account;}
// usage:const account: Account = client.accounts.retrieve()Configuring a Model
Section titled “Configuring a Model”A model doesn’t have to use a schema from #/components/schemas/*. It can refer to a schema anywhere in your OpenAPI spec using a full JSON Reference from the root.
For schemas under #/components/schemas/*, you don’t need to specify the whole JSON reference; you can just use the last path segment.
For example, in the Stainless config example above, instead of
models: account: '#/components/schemas/Account'you could use:
models: account: AccountsThe model configuration can also accept more options, if you need them. Please check out our full reference docs here.
When to Define a Model
Section titled “When to Define a Model”In general, you should define models for the “nouns” in your API: the objects your API operations create, read, update, and delete. But there aren’t hard and fast rules about which schemas you should define models for.
For example, suppose your OpenAPI spec defines User and Address schemas, and a User has a nested Address property. Whether you should define a model for Address depends on how it’s used in your API.
These rules of thumb can help you decide when to define a model:
-
Each resource in your Stainless config should have a corresponding model. In the example above,
accountsincludes anaccountmodel. -
Use the “rule of three”: if a schema is used in three or more places in your OpenAPI spec, it should probably be a model.
Exception:
Don’t define a model if you expect these uses to diverge later, to avoid breaking changes to your SDK. Let’s say you have a pet store API with
DogandCatschemas that includes acareInstructionsproperty. Right nowcareInstructionsonly includes the pet’s meal schedule, but eventually thecareInstructionsproperty on dogs might also include their walk schedule. If we define aCareInstructionsmodel, the SDK will look like this:type Dog {...care_instructions: CareInstructions}type Cat {...care_instructions: CareInstructions}type CareInstructions {daily_meals: number}We’re better off not defining a model, so the SDK looks like this:
type Dog {...care_instructions: { daily_meals: number }}type Cat {...care_instructions: { daily_meals: number }} -
Pay attention to the suggestions from the autoguesser and diagnostics in SDK Studio. The autoguesser will try to define models where appropriate, and SDK Studio will make suggestions about schemas that could be defined as models.
-
Most importantly, use your best judgment. Define a model whenever it will make your SDK more usable and more readable, even if it violates these rules of thumb.
Shared Models
Section titled “Shared Models”In general, a model belongs under the most closely related resource, and each resource should have at least one model. All of our SDKs are designed so that you can put a model where it most logically belongs without worrying about dependency issues.
There are cases where a model doesn’t belong under one resource. A common example of
that might be something like #/components/schemas/Address or #/components/schemas/Contact. We have a
special resource called $shared where these kinds of models should be defined.
resources: $shared: models: address: '#/components/schemas/Address' contact: '#/components/schemas/Contact'Methods
Section titled “Methods”Methods in the Stainless config translate to functions in your SDK that make API calls. Usually, you only need to specify the endpoint the function will call (including HTTP method):
resources: accounts: methods: list: get /accountsYou can include more properties to configure the method in more detail:
resources: accounts: methods: list: type: http endpoint: get /accounts deprecated: Use the /user endpoint insteadFor standard CRUD operations, we recommend using these common method names: create, update, delete, retrieve, and list.
Authentication
Section titled “Authentication”The Stainless Generator defines your SDK’s authentication using the #/security and #/components/securitySchemes particulars from your OpenAPI spec. By default, Stainless SDKs are set to authenticate using an environment variable:
client_settings: opts: auth_token: type: string # Whether this client option is required to instantiate the client: nullable: false # Whether this value should be read from an env: read_env: ORG_AUTH_TOKEN auth: { security_scheme: BearerAuth }
# optional, overrides the OpenAPI spec's top-level security key, required if it isn't presentsecurity: - BearerAuth: []For more complicated authentication schemes, or should the Stainless Generator fail to configure yours correctly, see the examples below.
The Stainless generator uses the top-level security as the security configuration supported by the SDKs, but
you may want to specify a different combination of security than the one used in your OpenAPI spec. The
security and security_schemes in the Stainless config overrides the values in the spec.
HTTP Bearer Authorization: Bearer <bearer-token>
The HTTP Bearer authentication method is configured like so:
# OpenAPIcomponents: security_schemes: MyBearerAuth: type: http scheme: bearer # optional, documentation purpose only bearerFormat: JWT
security: - MyBearerAuth: {}# Stainless configclient_settings: opts: my_bearer_token: # or `token`, `bearer_token`, `api_key`, etc. type: string read_env: ORG_BEARER_TOKEN auth: { security_scheme: MyBearerAuth }API Key <Header>: <API Key>
An API key in a header authentication method is configured like so:
# OpenAPIcomponents: security_schemes: MyApiKeyAuth: type: apiKey name: My-Api-Key in: header
security: - MyApiKeyAuth: {}# Stainless configclient_settings: opts: my_api_key: # or `token`, `auth_token`, etc. type: string read_env: ORG_API_KEY_TOKEN auth: { security_scheme: MyApiKeyAuth }HTTP Basic Authorization: Basic <base64(username:password)>
The HTTP Basic authentication method is configured like so:
# OpenAPIcomponents: security_schemes: MyBasicAuth: type: http scheme: basic
security: - MyBasicAuth: {}# Stainless configclient_settings: opts: my_username: type: string read_env: ORG_MY_USERNAME_TOKEN auth: { security_scheme: MyBasicAuth, role: 'username' } my_password: type: string read_env: ORG_MY_PASSWORD_TOKEN auth: { security_scheme: MyBasicAuth, role: 'password' }OAuth 2.0 Client Credentials
Stainless fully supports OAuth client_credentials. For this grant type, our SDKs handle initial authorization and the entire token management lifecycle.
Stainless uses the OAuth configuration in your OpenAPI spec if present, or you can specify an OAuth configuration in your Stainless config.
Example security configuration in the Stainless config:
security:- OAuth2: []
security_schemes: OAuth2: type: oauth2 flows: clientCredentials: tokenUrl: >- https://example.com/oauth2/token?grant_type=client_credentials scopes: # ...You must also define client options for the client_id and client_secret parameters in your Stainless config so users of your SDKs can provide those values. The names of these parameters in opts can be customized; the important part is to specify the correct auth information for each (especially auth.role, which must be either client_id or client_secret).
client_settings: opts: client_id: type: string auth: security_scheme: OAuth2 role: client_id read_env: ORG_CLIENT_ID client_secret: type: string auth: security_scheme: OAuth2 role: client_secret read_env: ORG_CLIENT_SECRETWith a configuration like the one above, users can provide a client_id and client_secret when initializing your SDK to use OAuth.
Behind the scenes, the SDK will fetch an access token from the configured tokenUrl when the first request is made, cache it, and use it for all following requests until the token expires. Upon expiration, the SDK will automatically obtain a new token.
Failed OAuth requests are automatically retried based on the retry configuration for the SDK. If all retries fail, an exception will be thrown or an error will be returned (whichever makes sense for the particular language being used).
Please reach out if you need help supporting your configuration.
OAuth 2.0 Authorization Codes
If you’re using authorization codes, we recommend you set up Bearer Authentication and add custom code to help your users convert authorization codes to access tokens. If you’re interested in generated helpers for this, or if you need additional guidance, reach out and let us know!
With this approach, initial authorization and token management are not handled by Stainless. We recommend you point your users to an OAuth SDK in their preferred language for handling these parts of the OAuth flow.
Once Stainless is configured to use Bearer Authentication, as seen in the example below, users of your SDKs will be able to authenticate with their access token.
OpenAPI spec:
securitySchemes: OAuth2: type: oauth2 flows: authorizationCode: authorizationUrl: https://example.com/oauth2/auth tokenUrl: https://example.com/oauth2/token scopes: # ...Stainless config:
client_settings: opts: access_token: type: string auth: security_scheme: BearerAuth read_env: MY_TEAM_ACCESS_TOKEN
security: - BearerAuth: []
security_schemes: BearerAuth: type: http scheme: bearerPlease reach out if you need help supporting your configuration.
Optional Auth
To specify that you accept no authentication, declare a security configuration with no key and no properties like so:
security: - BearerAuth: {} - {} # <= No authREADME.md configuration
Section titled “README.md configuration”We currently support configuration of the various code snippets in the README.md of a generated SDK.
readme: example_requests: default: type: request endpoint: post /cards params: type: SINGLE_USE headline: type: request endpoint: put /cards params: type: SINGLE_USE account_id: 123 pagination: type: request endpoint: get /cards params: limit: 30The headline example is the first usage of the API that your users see in the README.md, so it should
be the most ‘important’ endpoint in your API.
The pagination example is required if you have configured pagination, and should point to a paginated
endpoint.
The default example is inherited by all other example requests, though you can manually override them if
there is a need to do so. Because of this, we suggest that you choose the most ‘standard’ endpoint in your
API.
Pagination
Section titled “Pagination”Configuring pagination with Stainless SDKs generates a helper to fetch the next page and an auto-iterator to easily loop through items in your API. The helper makes it easy to manually paginate when needed and the auto-iterator makes consuming a list as natural as a for loop, with the iterator automatically fetching the next page when needed:
const iter: OffsetPage<Account> = await client.accounts.list()
for await (const account in iter) { if (account.name === 'Michael') { console.log(account.id) }}Pagination Scheme
Section titled “Pagination Scheme”Stainless will only use recognized pagination schemes when generating SDKs. If a method isn’t being paginated,
make sure that it has the required schema for the request and response, and that it matches the schema
in the OpenAPI spec. If a method is improperly paginated, make sure that the
x-stainless-pagination-property is set correctly.
The request and response must include all parameters and fields needed for pagination. The request
might have a limit, offset, page number, page size, or start ID; the response might have the data, a link
to the next page, or the next start ID.
You can also include parameters that aren’t strictly necessary for pagination to work. For example, it might
be appropriate to add a request parameter like sort_by, which might be an enum of the ways you can sort
across your API.
Here’s an example of a pagination scheme Stainless recognizes:
pagination: - # Name for the pagination scheme. # Stainless will use this name when generating code for pagination helpers. name: cursor_page # Type of pagination your API uses, used to set logic for fetching each page. # For the list of allowed types, see the "Pagination Type" section below. type: cursor_id request: # Specifies that paginated endpoints take a `starting_after` query param, # which pagination helpers pass cursor IDs to. starting_after: # This param name is the one your API uses. type: string x-stainless-pagination-property: # Defines the purpose of the param. purpose: next_cursor_id_param response: # Specifies that paginated responses have a `data` field # containing the contents of the requested page. data: type: array items: type: unknown x-stainless-pagination-property: purpose: items # Specifies that responses have a `next_cursor` field, # which pagination helpers will take the next cursor ID from. next_cursor: type: string x-stainless-pagination-property: purpose: next_cursor_field # Optional. Specifies where pagination parameters are located. # Can be `query` (default) or `body`. param_location: queryPagination Type
Section titled “Pagination Type”The first step in configuring pagination is to define the type of pagination your API is using. The types of pagination Stainless supports are:
cursor: A pagination scheme that uses a cursor (a string or a number to indicate the element to start from) to paginate through a list of items.cursor_id: Similar to cursor pagination, but the cursor comes from an id property of the response items.cursor_url: A pagination scheme that simply returns a URL to fetch from to retrieve the next page of results.offset: A simple pagination scheme that uses an offset (the number of entries to skip) and limit (number of elements to fetch) to paginate through a list of items.page_number: Similar to offset pagination, but instead of an offset, which indexes on the items,page_numberindexes on chunks of elements to skip.
Cursor Pagination
pagination: - name: my_cursor_page type: cursor request: next: type: string x-stainless-pagination-property: purpose: next_cursor_param limit: type: integer response: my_data: type: array items: type: object next: type: string x-stainless-pagination-property: purpose: next_cursor_fieldExample OpenAPI Specification
openapi: 3.1.0paths: /accounts: get: parameters: - name: next in: query schema: type: string - name: limit in: query schema: type: integer responses: '200': description: 'success!' content: application/json: schema: type: object properties: my_data: type: array items: type: object next: type: stringCursor ID Pagination
pagination: - name: my_cursor_id_page type: cursor_id request: starting_after: type: string x-stainless-pagination-property: purpose: next_cursor_id_param ending_before: type: string x-stainless-pagination-property: purpose: previous_cursor_id_param limit: type: integer response: my_data: type: array items: type: object properties: id: type: string x-stainless-pagination-property: purpose: cursor_item_id required: - idExample OpenAPI Specification
openapi: 3.1.0paths: /accounts: get: parameters: - name: starting_after in: query schema: type: string - name: ending_before in: query schema: type: string - name: limit in: query schema: type: integer responses: '200': description: 'success!' content: application/json: schema: type: object properties: my_data: type: array items: type: object properties: id: type: stringCursor URL Pagination
pagination: - name: cursor_url_page type: cursor_url request: page_size: type: integer response: data: type: array x-stainless-pagination-property: purpose: items next: type: string x-stainless-pagination-property: purpose: cursor_url_field from_header: LinkExample OpenAPI Specification
openapi: 3.1.0paths: /paginated/cursor_url: get: description: Test case for cursor_url pagination parameters: - name: page_size in: query schema: type: integer responses: '200': description: OK content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/MyModel' next: type: string format: uri description: The URL for the next page required: - data - next_pageOffset Pagination
pagination: - name: my_offset_page type: offset request: my_offset: type: integer description: The number of elements to skip. # this tells us to modify this param when getting the next page x-stainless-pagination-property: purpose: offset_count_param my_limit: type: integer description: The maximum number of elements to fetch. response: my_data: type: array items: type: object my_total: type: integer x-stainless-pagination-property: # total number of elements in the list purpose: offset_total_count_field my_count: type: integer x-stainless-pagination-property: # where to start the next page purpose: offset_count_start_fieldExample OpenAPI Specification
openapi: 3.1.0paths: /accounts: get: parameters: - name: my_offset in: query schema: type: integer - name: my_limit in: query schema: type: integer responses: '200': content: application/json: schema: type: object properties: my_data: type: array items: type: object my_total: type: integer my_count: type: integerPage Number Pagination
pagination: - name: my_page_number_page type: page_number request: page: type: integer x-stainless-pagination-property: purpose: page_number_param page_size: type: integer response: data: type: array items: type: object page: type: integer x-stainless-pagination-property: purpose: current_page_number_field last_page: type: integer x-stainless-pagination-property: purpose: total_page_count_fieldExample OpenAPI Specification
openapi: 3.1.0paths: /accounts: get: description: Example case for page_number pagination parameters: - name: page in: query schema: type: integer - name: page_size in: query schema: type: integer responses: '200': description: OK content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/MyModel' last_page: type: integer description: The last page number page: type: integer description: The page numberMultiple pagination schemes
Section titled “Multiple pagination schemes”The pagination section accepts multiple pagination schemes. We match each endpoint against the defined
pagination schemes by making sure the relevant request parameters/response fields exist for that endpoint
and have the correct type.
In cases where it’s ambiguous or you want to explicitly assert that a method matches my_offset_page, you can
provide paginated: my_offset_page and the generator reports an error if it doesn’t match that specific page.
See the config reference for pagination for various examples and edge cases.
Other Pagination Properties
Section titled “Other Pagination Properties”Retrieving pagination properties from headers
Section titled “Retrieving pagination properties from headers”If your API returns pagination values in headers, you can use the from_header property to specify the header to read the value from.
pagination: - name: page_cursor_from_headers type: cursor request: ... response: my_cursor: type: string nullable: true x-stainless-pagination-property: purpose: next_cursor_field from_header: 'X-My-Cursor'Retrieving pagination properties from nested items
Section titled “Retrieving pagination properties from nested items”If your API returns pagination values nested within the response, you can configure Stainless to read them from the nested items.
Example response:
{ "data": { "items": [{}, {}] }, "pagination_object": { "next_page": "next_page_cursor", "previous_page": "previous_page_cursor" }}pagination: - name: page_cursor_nested_items type: cursor_id request: ... response: data: type: object properties: items: x-stainless-pagination-property: purpose: items type: array items: {} pagination_object: type: object properties: next_page: type: string x-stainless-pagination-property: purpose: next_cursor_id_param previous_page: type: string x-stainless-pagination-property: purpose: previous_cursor_id_paramTop level arrays in response
Section titled “Top level arrays in response”You can use the is_top_level_array property to indicate that the response is a top-level array.
Example response:
[ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" }]pagination: - name: top_level_array type: offset request: ... response: items: type: array items: {} x-stainless-pagination-property: is_top_level_array: trueWebhooks
Section titled “Webhooks”If your OpenAPI specification includes a top-level webhooks section, Stainless can generate a helper function to convert your webhook events into the types you defined.
app.post('/webhook', async (req, res) => { const event: MyWebhookEvent = client.webhooks.unwrap( req.body.toString(), req.headers, ); ...});To do so, add a method to your Stainless config using the type webhook_unwrap:
resources: webhooks: methods: unwrap: type: webhook_unwrap discriminator: event_typeThis will generate client.webhooks.unwrap(payload), a method that parses HTTP payloads. The discriminator is the name of the field used to differentiate between the various payloads in your webhooks specification (if multiple are specified).
Webhook verification
Section titled “Webhook verification”Stainless also supports the Standard Webhooks specification. If the webhook requests do include valid webhook-id, webhook-timestamp, and webook-signature HMAC-SHA256 signature headers, you can also provide a key that will be used to verify the signatures and ensure they have been sent by an authorized server.
POST /webhook HTTP/1.1Host: example.com...webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OA=webhook-timestamp: 1614265330webhook-id: msg_p5jXN8AQM9LWM0D4loKWxJek...Here is an example Stainless configuration:
resources: webhooks: methods: unwrap: type: webhook_unwrap # The literal string "webhook_unwrap" discriminator: event_type # The field used to figure out which type was sent webhook_key_opt: webhook_key # The name of a client optionclient_settings: opts: webhook_key: # Referenced by `webhook_key_opt` type: string read_env: ORG_WEBHOOK_KEY nullable: trueThe webhook_key_opt setting refers to the name of a client option (in this case, client_settings.opts.webhook_key) where you will store the key used to verify signatures.
You can configure how this client option is loaded in the client_settings section (in the example, through the ORG_WEBHOOK_KEY environment variable) and whether or not it’s allowed to be null.
A nullable key may be useful if some of your users won’t be using the webhooks feature or if users won’t have the key until after the client has been initialized.
The webhook key you use must be base64-encoded and may have the optional prefix whsec_ in front of the base64 value.
If a webhook_key_opt is specified for the method (and there is a client option by that name), we will generate a method for client.webhooks.unwrap(payload, headers) that requires you to pass in the webhook request’s headers for verification.
Client
Section titled “Client”Extra client arguments
Section titled “Extra client arguments”You can define extra client arguments, which generally appears as an extra argument on the client
constructor of each SDK (in Go, it appears as an extra RequestOption). These are generally used for
supplying values for authentication methods, but can be also used for extra headers and
more.
client_settings: opts: pet_store_version: type: string # can be a string, boolean, or a number nullable: true # makes this an optional argument default: v3 # the default pet store version to use
read_env: PETSTORE_VERSION send_in_header: 'X-Petstore-Version'const client = new Petstore({ apiKey: '...', petStoreVersion: 'v2', // sends 'X-Petstore-Version: v2'});Default headers
Section titled “Default headers”Default headers are headers we add to every request made by the SDK. We send platform headers so that you can collect metrics on the languages and platforms your users use. For all SDKs, we send the following:
| Headers | Description |
|---|---|
X-Stainless-Arch | The architecture, such as x32, x64, arm, aarch64, or other:xxx. |
X-Stainless-Lang | The language, such as typescript, python, java, kotlin, or go. |
X-Stainless-OS | The OS, such as Android, MacOS, Windows, FreeBSD, OpenBSD, Linux, or Other:xxx. |
X-Stainless-Package-Version | The package version, such as v2.3.1. |
X-Stainless-Read-Timeout | The timeout, in seconds, between receiving response chunks, such as 3. Not sent if no read timeout is configured. |
X-Stainless-Retry-Count | Which retry number the request is for, such as 0 for the first request, 1 for the first retry, 2 for the second retry, and so on. |
X-Stainless-Runtime | The runtime, such as node or CPython. |
X-Stainless-Runtime-Version | The runtime version, such as v14.8.0. |
X-Stainless-Timeout | The timeout, in seconds, for the entire request, such as 10. Not sent if no overall timeout is configured. |
We also send some extra headers for each language:
| Python Headers | Description |
|---|---|
X-Stainless-Async | Whether or not the AsyncClient was used. |
| Java/Kotlin Headers | Description |
|---|---|
X-Stainless-OS-Version | The OS version, such as 14.4. |
For requests made by a Stainless-generated MCP server, we will also send some extra headers:
| MCP Headers | Description |
|---|---|
X-Stainless-MCP | Always true. |
Retries
Section titled “Retries”Our clients retry connection errors (for example, a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors.
By default, our clients retry 2 times (so a total of 3 requests) using the exponential backoff strategy with an initial delay of 0.5s and a max delay of 8s. We also add a jitter of 25% to spread out requests.
This can be configured by you for your users:
client_settings: default_retries: # 5 retries are made, with the interval [1, 2, 4, 8, 10 (capped by the max)] # not accounting for jitter max_retries: 5 initial_delay_seconds: 1 max_delay_seconds: 10Or it can be changed by the user in each language’s SDK:
client = new Petstore({ maxRetry: 3,});Each request includes an X-Stainless-Retry-Count header so you know how often clients retry. See Default headers for details.
Disabling retries
Section titled “Disabling retries”Retries can be disabled in two ways. Your users can disable this at at the client level by setting max_retries to 0. For example:
client = new Petstore({ maxRetry: 0, // Doesn't retry at all});Alternatively, your API can direct our SDKs to not retry by sending the X-Should-Retry header in your responses.
Retry-After and Retry-After-Ms
Section titled “Retry-After and Retry-After-Ms”Our SDKs also respect the Retry-After
header sent by the API, which defines
in integers how many seconds we should wait before making another request. We also support the
Retry-After-Ms header which is less standard but gives more fine-grained control over timings in
milliseconds.
The clients only respect values that are “reasonable” which are positive values less than a minute.
X-Should-Retry
Section titled “X-Should-Retry”Additionally, the clients support the X-Should-Retry header, which can be used to explicitly control whether
a request should be retried. This header must be explicitly set to either X-Should-Retry: true or
X-Should-Retry: false to be used as an override.
When set to true, it forces a retry attempt even if the request would not normally be retried based on the
default retry rules. When set to false, it prevents a retry attempt even if the request would normally be
retried. If the header is not present or set to any other value, the default retry behavior is used.
We recommend using the header over other methods to control the retry behavior on special status codes or other situations specific to your API, as it can be used by all consumers of the API and can be configured independently of the version of the SDK.
Timeouts
Section titled “Timeouts”In addition to retries, our clients also have a default timeout of 60 seconds, which can be configured.
client_settings: default_timeout: PT60S # ISO8601 or number of milliseconds. This is 60 secondsIdempotency key
Section titled “Idempotency key”Idempotency Keys can prevent errors where multiple retried requests are interpreted as separate requests. Our clients retry connection errors and certain status codes by default to create robust integrations, so we recommend that your API supports idempotency keys, especially for critical endpoints.
You can configure idempotency keys by specifying client_settings.idempotency like so:
client_settings: idempotency: header: 'Idempotency-Key' # or a header you prefer, like 'X-Request-Id'We send the configured header with a value in the format stainless-retry-{random_uuid} on all
non-GET requests. This header is also possible to override in every SDK that we generate.
File uploads
Section titled “File uploads”Our SDKs support file uploads out of the box, via multipart/form-data requests.
File parameters can be defined in the OpenAPI spec by specifying a multipart/form-data request with
parameters that are type: string and format: binary.
paths: /files: post: summary: Upload a file requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: The file to upload file_metadata: type: string description: > Some other property that you can add, serialized as a field in multipart/form-data required: - file responses: '200': description: File uploaded successfullyEach SDK provides convenient ways to handle file uploads appropriate for that language. Below is an example in TypeScript:
await client.files.create({ file: fs.createReadStream('./local-file.txt'), 'content.txt') });// orawait client.files.create({ file: new File(['my bytes'], 'content.txt') });Similar helpers exist in other languages. For example, in Python:
with open('content.txt', 'rb') as f: client.files.create(file=f)See the README.md of each SDK for language-specific documentation.
Enterprise
Section titled “Enterprise”OpenAPI transformations
Section titled “OpenAPI transformations”If your OpenAPI specification is not perfect and/or hard to modify because it’s auto-generated, we can work with you to identify problems in your OpenAPI spec and fix issues on our end with OpenAPI transforms. Contact sales.
SSE streaming
Section titled “SSE streaming”Streaming Server-Sent Events are supported by Stainless codegen at the Pro and Enterprise tiers. When configured, we will generate bindings for your users to
consume streams ergonomically and conveniently, for example in for await loops and more. Contact sales.
If you’re on the Free or Starter tier you can implement streaming support using custom code.
We can also help write and design helpers to make your streamed APIs more powerful for your users.