Configure your SDKs
Learn how to configure your SDKs in Stainless. Organize resources and subresources, map OpenAPI schemas to reusable models, and set up methods and endpoints. This makes your SDKs clear, maintainable, and user-friendly, with easy control over language features and deprecation.
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 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 method is a function that makes an API request.
- A model represents a type that can be reused throughout your SDKs.
- 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()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.
Controlling method generation by language
Section titled “Controlling method generation by language”You can use the skip and only properties to control whether a method is generated for specific languages.
Skip generation for specific languages
Section titled “Skip generation for specific languages”To skip a method for one or more languages, use the skip property with an array of SupportedLanguage values:
resources: products: methods: create: endpoint: post /products skip: - terraformThis will generate the create method for all languages except Terraform.
You can also skip multiple languages:
resources: products: methods: create: endpoint: post /products skip: - terraform - cliTo skip a method for all languages, use skip: true:
resources: products: methods: internal_method: endpoint: get /internal skip: trueGenerate only for specific languages
Section titled “Generate only for specific languages”The only property is the opposite of skip — it generates the method only for the specified languages:
resources: products: methods: terraform_only_method: endpoint: post /terraform-specific only: - terraformTerraform-specific control
Section titled “Terraform-specific control”For Terraform, you also have the option to use terraform.method: skip to exclude a method from Terraform generation without affecting other languages:
resources: products: methods: create: endpoint: post /products terraform: method: skipThis is equivalent to using skip: [terraform] but is more explicit when working with Terraform-specific configurations. For more information on Terraform configuration, see the Terraform guide.
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'