Skip to content
FeedbackDashboard
Getting started
Configure SDKs

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.

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 ┐
// │
// V
const item: Task = client.tasks.create({
// ^ ^
// | |
// | └ from `create` method
// |
// └ from `task` model
name: "Grocery shopping",
deadline: "2025-05-01"
});
// from `tags` subresource of `tasks`
// │
// V
client.tasks.tags.list(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 config
organization:
...
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:

  1. 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 task model becomes a Task type in Python and TypeScript. Similarly, a blog_post model would be renamed BlogPost in those languages.

    Method names in Go

    The Go SDK generator goes one step further. To follow Go’s naming conventions, it translates the methods create and retrieve to the function names New and Get, respectively.

  2. Methods are mapped to API calls, so the create function in the examples above will send a POST request to /v1/tasks.

  3. Models in the Stainless config are mapped to schemas defined in the OpenAPI spec. In this example, the task model in the Stainless config points to the Task schema, which defines the name and deadline properties in the SDK:

# OpenAPI Spec
components:
schemas:
Task:
type: object
properties:
name:
type: string
deadline:
type: string
format: date

[reference]

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 /health

Then the SDK usage would be:

health = client.health()

[reference]

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 /accounts

You 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 instead

For standard CRUD operations, we recommend using these common method names: create, update, delete, retrieve, and list.

You can use the skip and only properties to control whether a method is generated 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:
- terraform

This 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
- cli

To skip a method for all languages, use skip: true:

resources:
products:
methods:
internal_method:
endpoint: get /internal
skip: true

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:
- terraform

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: skip

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

[reference]

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 config
resources:
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()

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: Accounts

The model configuration can also accept more options, if you need them. Please check out our full reference docs here.

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:

  1. Each resource in your Stainless config should have a corresponding model. In the example above, accounts includes an account model.

  2. 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 Dog and Cat schemas that includes a careInstructions property. Right now careInstructions only includes the pet’s meal schedule, but eventually the careInstructions property on dogs might also include their walk schedule. If we define a CareInstructions model, 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 }
    }
  3. 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.

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

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'