--- title: How to Configure SDK resources, methods, and models for an intuitive SDK | Stainless SDKs description: 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](https://app.stainless.com). ## 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**](#resources) 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**](#methods) is a function that makes an API request. - A [**model**](#models) 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.) - [TypeScript](#tab-panel-0) - [Python](#tab-panel-1) - [Go](#tab-panel-2) ``` 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) ``` ``` from todo_ninja import TodoNinja, Task client = TodoNinja() # from `tasks` resource # │ ┌ from `create` method # | | # V V item: Task = client.tasks.create( # ^ # | # └ from `task` model name="Grocery shopping", date=date.fromisoformat("2025-05-01"), ) # from `tags` subresource of `tasks` # │ # V client.tasks.tags.list(task_id) ``` ``` client := todoninja.NewClient() var task* todoninja.Task // from `task` model var err error // from `tasks` resource // | // V task, 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` // | // V client.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 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 ... ``` For more details on the other config sections, see below or consult the [Stainless config reference](/docs/reference/config/index.md). 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 ``` ## Resources [\[reference\]](/docs/reference/config#resource/index.md) 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() ``` ## Methods [\[reference\]](/docs/reference/config#method/index.md) 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`. ### 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 To skip a method for one or more languages, use the `skip` property with an array of [SupportedLanguage](/docs/reference/config#supported-language/index.md) 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 ``` #### 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: - terraform ``` #### 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: 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](/docs/targets/terraform/index.md). ## Models [\[reference\]](/docs/reference/config#model/index.md) 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() ``` ### 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](https://swagger.io/docs/specification/using-ref/) 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 ``` Your schemas could have `$ref`s, or be entirely inline, or a mix of both---it won’t affect the names that we use. This means you can refactor your OpenAPI spec without worrying about breaking changes. The model configuration can also accept more options, if you need them. Please check out our [full reference docs here](/docs/reference/config/index.md). ### 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: 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. ### 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' ```