Background image

OpenAPI Tips

Defining OpenAPI Servers - The Where To Your API's How

Nolan Sullivan

Nolan Sullivan

November 24, 2023

Featured blog post image
Success Icon

Announcing: OpenAPI Reference

Hi! These blog posts have been popular, so we’ve built an entire OpenAPI Reference Guide to answer any question you have.

It includes detailed information on servers.

Happy Spec Writing!

In this post, we’ll take a detailed look at how the OpenAPI Specification (OAS) allows us to define servers in our OpenAPI documents (OADs), then at a few tips to help you provide the best developer experience for your users.

This aspect of API design is often overlooked, but specifying servers in a flexible and robust way can ensure your users know exactly where to send API calls, allows users the flexibility to pick a specific server based on their preference or data-privacy requirements, and allows your developers to switch between development and production environments while testing without editing code.

A Brief History of Base URLs in Swagger (OAS 2.0)

If you have some experience with Swagger 2.0, the predecessor to OpenAPI 3.0, you may recall that specifying servers for your API was somewhat rigid. We’re including this to show you how to convert your Swagger 2.0 base URL definition to OpenAPI 3.x later on. If you don’t have a Swagger 2.0 document, you can safely skip this section.

In Swagger 2.0, we could define three fields to tell API users where to send requests: host, basePath, and schemes. The host specified the domain name or IP, the basePath defined the base path for the API, and schemes determined the protocols (HTTP or HTTPS) as an array.

Here’s an example of the relevant Swagger 2.0 fields:

swagger-2.0.yaml
swagger: "2.0"
host: api.example.com
basePath: /v1
schemes:
- https
- http
# ...

To construct the base URL for requests, the API user should select a scheme, then concatenate the strings to form the URL: schemes[n] + host + basepath.

Screenshot of Swagger 2.0 editor showing the scheme selector as the only URL-related option

The screenshot above illustrates the scheme selector in the Swagger 2.0 editor. The scheme selector is the only option for building a base URL in Swagger 2.0.

Servers in OAS 3.x - Controlled Flexibility

In response to the need for more flexibility, OAS 3.0 introduces the optional servers schema. The servers schema allows API users to select a base URL from an array of servers, some of which can include variables - allowing for the construction of more complex base URLs.

Each server in the servers array consists of an object with at least one field, url. Each server object can also contain an optional description field, as well as an optional array of variables.

Let’s look at an OpenAPI 3.1.0 document with three different server objects to illustrate the available options, and how they influence the Swagger UI server selector.


The first server object is the simplest. It contains only a url field, which is a string containing the base URL for the API: https://speakeasy.bar.


The second server object contains a url field and a description field. The description field is a string that can be used to describe the server. This is useful for providing additional information about the server, such as its location or purpose.


In our example, the description field is used to describe the server as the staging server.


The third server object contains a url field, a description field, and a variables field. The variables field is an array of objects that define variables that can be used to construct the base URL.


In this example, the URL is a template string that contains two variables: organization and environment.


Each variable’s name is defined by the object’s key.


The default field is a string that defines the default value for the variable.


The enum field is an array of strings that define the possible values for the variable.


The description field is a string that can be used to describe the variable. This is useful for providing additional information about the variable, such as its purpose.

This field is not used by the OpenAPI documentation generator, but it can be used by other tools to provide additional information to API users.

openapi.yaml
openapi: 3.1.0
servers:
- url: https://speakeasy.bar
- description: The staging server.
url: https://staging.speakeasy.bar
- description: A per-organization and per-environment API.
url: https://{organization}.{environment}.speakeasy.bar
variables:
environment:
default: prod
description: The environment name. Defaults to the production environment.
enum:
- prod
- staging
- dev
organization:
default: api
description: The organization name. Defaults to a generic organization.
info:
contact:
email: support@speakeasy.bar
name: Speakeasy Support
url: https://support.speakeasy.bar
description: A secret underground bar that serves drinks to those in the know.
summary: A bar that serves drinks.
title: The Speakeasy Bar
version: 1.0.0
components:
schemas:
APIError:
properties:
code:
title: Code
type: string
details:
title: Details
type: object
message:
title: Message
type: string
title: APIError
type: object
AuthenticatePostRequest:
properties:
password:
title: Password
type: string
username:
title: Username
type: string
title: AuthenticatePostRequest
type: object
AuthenticatePostResponse:
properties:
token:
title: Token
type: string
title: AuthenticatePostResponse
type: object
Drink:
properties:
name:
description: The name of the drink.
examples:
- Old Fashioned
- Manhattan
- Negroni
title: Name
type: string
price:
description: The price of one unit of the drink in US cents.
examples:
- 1000
- 1200
- 1500
title: Price
type: number
productCode:
description: The product code of the drink, only available when authenticated.
examples:
- AC-A2DF3
- NAC-3F2D1
- APM-1F2D3
title: Productcode
type: string
stock:
description: The number of units of the drink in stock, only available when
authenticated.
title: Stock
type: integer
type:
$ref: '#/components/schemas/DrinkType'
required:
- name
- price
title: Drink
type: object
DrinkType:
description: An enumeration.
enum:
- cocktail
- non-alcoholic
- beer
- wine
- spirit
- other
title: DrinkType
Error:
properties:
code:
title: Code
type: string
message:
title: Message
type: string
title: Error
type: object
Ingredient:
properties:
name:
description: The name of the ingredient.
examples:
- Sugar Syrup
- Angostura Bitters
- Orange Peel
title: Name
type: string
productCode:
description: The product code of the ingredient, only available when authenticated.
examples:
- AC-A2DF3
- NAC-3F2D1
- APM-1F2D3
title: Productcode
type: string
stock:
description: The number of units of the ingredient in stock, only available
when authenticated.
examples:
- 10
- 5
- 0
title: Stock
type: integer
type:
$ref: '#/components/schemas/IngredientType'
required:
- name
- type
title: Ingredient
type: object
IngredientType:
description: An enumeration.
enum:
- fresh
- long-life
- packaged
title: IngredientType
Order:
properties:
productCode:
description: The product code of the drink or ingredient.
examples:
- AC-A2DF3
- NAC-3F2D1
- APM-1F2D3
title: Productcode
type: string
quantity:
description: The number of units of the drink or ingredient to order.
minimum: 1.0
title: Quantity
type: integer
status:
allOf:
- $ref: '#/components/schemas/Status'
description: The status of the order.
type:
$ref: '#/components/schemas/OrderType'
required:
- type
- productCode
- quantity
- status
title: Order
type: object
OrderType:
description: An enumeration.
enum:
- drink
- ingredient
title: OrderType
Status:
description: An enumeration.
enum:
- pending
- processing
- complete
title: Status
Webhook:
description: An enumeration.
enum:
- stockUpdate
title: Webhook
WebhooksSubscribePostRequest:
properties:
url:
title: Url
type: string
webhook:
$ref: '#/components/schemas/Webhook'
title: WebhooksSubscribePostRequest
type: object
paths:
/authenticate:
post:
description: Authenticate with the API by providing a username and password.
operationId: authenticate_authenticate_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatePostRequest'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatePostResponse'
description: Successful Response
5XX:
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Server Error
default:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Default Response
summary: Authenticate
tags:
- authentication
/drink/{name}:
get:
description: Get a drink.
operationId: get_drink_drink__name__get
parameters:
- in: path
name: name
required: true
schema:
title: Name
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Drink'
description: Successful Response
5XX:
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Server Error
default:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Default Response
summary: Get Drink
tags:
- drinks
/drinks:
get:
description: Get a list of drinks.
operationId: list_drinks_drinks_get
parameters:
- in: query
name: type
required: false
schema:
$ref: '#/components/schemas/DrinkType'
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Drink'
title: Response List Drinks Drinks Get
type: array
description: Successful Response
5XX:
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Server Error
default:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Default Response
summary: List Drinks
tags:
- drinks
/ingredients:
get:
description: Get a list of ingredients.
operationId: list_ingredients_ingredients_get
requestBody:
content:
application/json:
schema:
items:
type: string
title: Ingredients
type: array
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Ingredient'
title: Response List Ingredients Ingredients Get
type: array
description: Successful Response
5XX:
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Server Error
default:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Default Response
summary: List Ingredients
tags:
- ingredients
/order:
post:
description: Create an order.
operationId: create_order_order_post
parameters:
- in: query
name: callback_url
required: false
schema:
title: Callback Url
type: string
requestBody:
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Order'
title: Body
type: array
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
description: Successful Response
5XX:
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Server Error
default:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Default Response
summary: Create Order
tags:
- orders
/webhooks/subscribe:
post:
description: Subscribe to webhooks.
operationId: subscribe_to_webhooks_webhooks_subscribe_post
requestBody:
content:
application/json:
schema:
items:
$ref: '#/components/schemas/WebhooksSubscribePostRequest'
title: Body
type: array
required: true
responses:
'200':
content:
application/json:
schema: {}
description: Successful Response
5XX:
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Server Error
default:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Default Response
summary: Subscribe To Webhooks
tags:
- configuration

Steps to Adapt Swagger 2.0 URL Definitions to OpenAPI 3.x

  1. Identify Swagger 2.0 URL Components: Extract the host, basePath, and schemes values from your Swagger 2.0 document.

  2. Create the servers array: Initiate a servers array in your OpenAPI 3.x document.

  3. Translate URL Components: For each scheme in your Swagger 2.0 document, create a server object in the OpenAPI 3.x servers array. Combine the scheme, host, and basePath to form the url field of each server object.

For example, if your Swagger 2.0 document contains the following URL components:

swagger-2.0.yaml
swagger: "2.0"
host: api.example.com
basePath: /v1
schemes:
- https
- http

You would create the following servers array in your OpenAPI 3.x document:

openapi.yaml
openapi: 3.1.0
servers:
- url: https://api.example.com/v1
- url: http://api.example.com/v1

Best Practices for Defining Servers in OpenAPI

When defining servers in an OpenAPI document, it’s crucial to strike a balance between flexibility and clarity. The goal is to provide enough information for users to understand and use the API effectively while allowing for various deployment scenarios.

Here are some best practices to keep in mind:

  1. Provide clear and concise descriptions: When using the description field, ensure it clearly indicates the purpose or specific use case of each server. This is particularly helpful in multiserver setups where different servers serve different roles, such as development, staging, and production.

  2. Use variables judiciously: Variables offer great flexibility but can also introduce complexity. Use them sparingly and only when necessary, such as when users have unique base URLs or need to select a specific environment.

  3. Document server variables thoroughly: If you use variables in your server URLs, provide clear documentation on what each variable represents and the valid values (enum) it can take. This documentation is crucial for users who need to understand how to construct the URLs correctly.

  4. Consider including common environment URLs: While variables offer flexibility, sometimes it’s easier for users if you explicitly list common environments (like production, staging, development) as separate server entries. This approach reduces the need for users to understand and manipulate variables.

  5. Validate server URLs regularly: Use automated tools to validate that the URLs in the servers section are up to date and operational. This check is particularly important for APIs that undergo frequent changes or have multiple deployment environments.

  6. Stay consistent across documents: If you manage multiple OpenAPI documents, maintain consistency in how servers are defined across them. This consistency helps users who work with multiple APIs in your suite, making it easier to understand and switch between them.

By following these best practices, you can ensure that your OpenAPI server definitions are both functional and user-friendly, enhancing the overall usability and accessibility of your API.

Advanced Server Definitions: Using Variables

Server variables offer significant flexibility and adaptability when defining servers. This approach is especially useful in scenarios where the base URL of an API might change based on different environments (like production, staging, or development) or other factors (like user-specific or regional settings). Here’s how to leverage variables effectively in your OpenAPI server definitions.

Understanding Variables in Server URLs

  1. Defining variables: Variables in OpenAPI are defined within the servers array. Each server object can have a variables field, which is an object itself. This object contains keys representing variable names, each with its set of properties.

  2. Properties of variables: The properties of a variable can include:

    • default: A mandatory field that specifies the default value of the variable.
    • enum: An optional array of possible values the variable can take.
    • description: An optional field to describe the variable’s purpose or usage.
  3. Using variables in URLs: Variables are used within the url field of a server object, enclosed in curly braces {}. For example, https://{environment}.api.example.com.

Best Practices for Using Variables

  1. Name variables clearly: Choose clear and descriptive names for your variables. Names like environment, region, or version can be more intuitive for users to understand and use.

  2. Use sensible defaults: Always provide sensible default values that point to the most common or recommended server configuration. This approach ensures that the API can be used out of the box without requiring users to modify the server URL.

  3. Restrict values with enums: When appropriate, use enum to restrict the values that a variable can take. This is particularly useful for environment variables where only specific values like production, staging, and development are valid.

  4. Descriptive documentation: Each variable should be accompanied by a description that explains its purpose and how it should be used. This is crucial for users who are unfamiliar with your API or its configuration options.

  5. Test variable-driven URLs: Ensure that all combinations of variables and their values lead to valid and accessible API endpoints. This testing is critical to avoid configuration errors that could make the API unusable.

Example of a Variable-Driven Server Definition

openapi.yaml
openapi: 3.1.0
servers:
- url: https://{environment}.api.example.com/v1
description: API server - selectable environment
variables:
environment:
default: production
enum:
- production
- staging
- development
description: Deployment environment of the API

In this example, the environment variable allows users to switch between different deployment environments without the need for separate server entries for each one.

Use variables in server definitions to enhance the flexibility and scalability of your API configurations, catering to a wide range of deployment scenarios and user needs.

Servers at the Path or Operation Level

While it’s common to define servers at the global level in OpenAPI documents, there are cases where specifying servers at the path or operation level is essential. This approach allows for more granular control, enabling different parts of the API to interact with different servers. It’s particularly useful in microservices architectures, where different services may reside on different servers, or when specific operations require a unique endpoint.

Overriding Servers at Path or Operation Level

  1. Path-level servers: You can specify servers for individual paths. This is useful when different paths in your API are hosted on different servers. For instance, if your API handles file uploads and general data requests separately, you might have different servers handling each.

  2. Operation-level servers: Similarly, servers can be specified for individual operations (GET, POST, PUT, DELETE, etc.). This granularity is beneficial when a specific operation, like a data submission (POST request), needs to be directed to a distinct server, like a secure data processing server.

Best Practices

  • Clear documentation: Clearly document why certain paths or operations use different servers. This clarity helps developers understand the API’s architecture and its interaction with various servers.

  • Consistency in structure: If you have different servers for different paths or operations, maintain a consistent structure in how these are defined to avoid confusion.

  • Fallback to global servers: Ensure that there is always a fallback server defined at the global level. This fallback acts as a default in cases where a path or operation doesn’t explicitly specify a server.

Example of a Server Definition at the Path Level

Consider an API where file uploads are handled by a dedicated server. Here’s how you might define this at the path level:

paths:
/upload:
servers:
- url: https://upload.api.example.com
description: Dedicated server for file uploads
post:
summary: Upload a file
# ... other operation details ...

In this example, any POST requests to the /upload path would be directed to the specified upload server, differentiating it from other operations in the API that use the default global server.

By strategically using servers at the path or operation levels, you can effectively manage different aspects of your API’s functionality, ensuring that each part interacts with the most appropriate server.

Case Studies: Effective Server Definitions in OpenAPI

We’ll now look at a few examples of how different companies have used OpenAPI server definitions to enhance their APIs’ usability and functionality.

Stripe: Path-Level Servers for File Uploads

Stripe’s OpenAPI documentation offers an excellent example of using path-level servers. In the Stripe API, the endpoint for file uploads is directed to the https://files.stripe.com/ server, optimized for handling large data transfers. This separation ensures that the file upload process does not interfere with the regular API traffic and provides a more efficient way to handle resource-intensive operations.

The following excerpt from Stripe’s OpenAPI document (opens in a new tab) illustrates this setup:

stripe.yaml
openapi: 3.0.0
paths:
/v1/files:
post:
operationId: PostFiles
servers:
- url: "https://files.stripe.com"
# ... other operation details ...
servers:
- url: "https://api.stripe.com"

This setup demonstrates a clear understanding of operational requirements and a commitment to providing a seamless user experience.

Stripe’s OpenAPI document could be further enhanced by providing a fallback global server in case the file server is unavailable and documenting the reason for using a separate server for file uploads.

Datadog: Dynamic Server Selection with Variables

Datadog’s API, known for its scalability and flexibility, leverages variables in server definitions to allow users to select their preferred region. This feature is particularly useful for global services where latency and data residency are critical considerations.

This excerpt from Datadog’s OpenAPI document (opens in a new tab) illustrates this setup.


In this example, users can dynamically choose the region closest to them, enhancing the performance and reliability of the API.


The site variable allows users to select the regional site for Datadog customers.


The enum field restricts the possible values to the available regional sites.


Datadog also uses operation-level servers to direct log uploads to the appropriate server.

datadog.yaml
openapi: 3.0.0
servers:
- url: https://{subdomain}.{site}
variables:
site:
default: datadoghq.com
description: The regional site for Datadog customers.
enum:
- datadoghq.com
- us3.datadoghq.com
- us5.datadoghq.com
- ap1.datadoghq.com
- datadoghq.eu
- ddog-gov.com
subdomain:
default: api
description: The subdomain where the API is deployed.
- url: "{protocol}://{name}"
variables:
name:
default: api.datadoghq.com
description: Full site DNS name.
protocol:
default: https
description: The protocol for accessing the API.
- url: https://{subdomain}.{site}
variables:
site:
default: datadoghq.com
description: Any Datadog deployment.
subdomain:
default: api
description: The subdomain where the API is deployed.
paths:
/api/v2/logs:
post:
operationId: SubmitLog
servers:
- url: https://{subdomain}.{site}
variables:
site:
default: datadoghq.com
description: The regional site for customers.
enum:
- datadoghq.com
- us3.datadoghq.com
- us5.datadoghq.com
- ap1.datadoghq.com
- datadoghq.eu
- ddog-gov.com
subdomain:
default: http-intake.logs
description: The subdomain where the API is deployed.
- url: "{protocol}://{name}"
variables:
name:
default: http-intake.logs.datadoghq.com
description: Full site DNS name.
protocol:
default: https
description: The protocol for accessing the API.
- url: https://{subdomain}.{site}
variables:
site:
default: datadoghq.com
description: Any Datadog deployment.
subdomain:
default: http-intake.logs
description: The subdomain where the API is deployed.

Conclusion: The Impact of Well-Defined Servers in API Usability

The way servers are defined in OpenAPI documents can significantly impact the usability and effectiveness of an API. Well-defined servers provide clear, accessible endpoints for different operational needs, improve the developer experience, and foster trust in the API’s reliability and efficiency. Key benefits include:

  • Enhanced clarity: Clear server definitions help developers understand where and how to interact with the API.
  • Operational flexibility: Using variables and specifying servers at different levels (global, path, and operation) allows for greater operational flexibility and efficiency.
  • Improved performance: Strategic server allocation, such as dedicated endpoints for resource-intensive tasks, optimizes API performance.
  • Global scalability: Variables that allow for regional server selection make the API more adaptable to global users, addressing concerns like latency and data residency.

Additional Resources and Tools for OpenAPI Server Definition

If you’re interested in deepening your understanding of OpenAPI server definitions, we recommend the following resources and tools:

  1. OpenAPI Specification: The official OpenAPI Specification (opens in a new tab) documentation provides comprehensive guidance on server definitions and other aspects of OpenAPI.
  2. Swagger Editor: An online tool that helps visualize and edit OpenAPI documents (opens in a new tab), making it easier to define and review server configurations.
  3. The OpenAPI Specification Explained: API Servers: A detailed guide to the OpenAPI Servers (opens in a new tab) that explains the different aspects of server definitions and how to use them effectively.
  4. Speakeasy documentation: Our documentation on how to configure your servers in OpenAPI while creating SDKs with Speakeasy.
CTA background illustrations

Speakeasy Changelog

Subscribe to stay up-to-date on Speakeasy news and feature releases.