OpenAPI Tips
Defining OpenAPI Servers - The Where To Your API's How
Nolan Sullivan
November 24, 2023
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"host: api.example.combasePath: /v1schemes:- 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
.
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: 3.1.0servers:- 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.barvariables:environment:default: proddescription: The environment name. Defaults to the production environment.enum:- prod- staging- devorganization:default: apidescription: The organization name. Defaults to a generic organization.info:contact:email: support@speakeasy.barname: Speakeasy Supporturl: https://support.speakeasy.bardescription: A secret underground bar that serves drinks to those in the know.summary: A bar that serves drinks.title: The Speakeasy Barversion: 1.0.0components:schemas:APIError:properties:code:title: Codetype: stringdetails:title: Detailstype: objectmessage:title: Messagetype: stringtitle: APIErrortype: objectAuthenticatePostRequest:properties:password:title: Passwordtype: stringusername:title: Usernametype: stringtitle: AuthenticatePostRequesttype: objectAuthenticatePostResponse:properties:token:title: Tokentype: stringtitle: AuthenticatePostResponsetype: objectDrink:properties:name:description: The name of the drink.examples:- Old Fashioned- Manhattan- Negronititle: Nametype: stringprice:description: The price of one unit of the drink in US cents.examples:- 1000- 1200- 1500title: Pricetype: numberproductCode:description: The product code of the drink, only available when authenticated.examples:- AC-A2DF3- NAC-3F2D1- APM-1F2D3title: Productcodetype: stringstock:description: The number of units of the drink in stock, only available whenauthenticated.title: Stocktype: integertype:$ref: '#/components/schemas/DrinkType'required:- name- pricetitle: Drinktype: objectDrinkType:description: An enumeration.enum:- cocktail- non-alcoholic- beer- wine- spirit- othertitle: DrinkTypeError:properties:code:title: Codetype: stringmessage:title: Messagetype: stringtitle: Errortype: objectIngredient:properties:name:description: The name of the ingredient.examples:- Sugar Syrup- Angostura Bitters- Orange Peeltitle: Nametype: stringproductCode:description: The product code of the ingredient, only available when authenticated.examples:- AC-A2DF3- NAC-3F2D1- APM-1F2D3title: Productcodetype: stringstock:description: The number of units of the ingredient in stock, only availablewhen authenticated.examples:- 10- 5- 0title: Stocktype: integertype:$ref: '#/components/schemas/IngredientType'required:- name- typetitle: Ingredienttype: objectIngredientType:description: An enumeration.enum:- fresh- long-life- packagedtitle: IngredientTypeOrder:properties:productCode:description: The product code of the drink or ingredient.examples:- AC-A2DF3- NAC-3F2D1- APM-1F2D3title: Productcodetype: stringquantity:description: The number of units of the drink or ingredient to order.minimum: 1.0title: Quantitytype: integerstatus:allOf:- $ref: '#/components/schemas/Status'description: The status of the order.type:$ref: '#/components/schemas/OrderType'required:- type- productCode- quantity- statustitle: Ordertype: objectOrderType:description: An enumeration.enum:- drink- ingredienttitle: OrderTypeStatus:description: An enumeration.enum:- pending- processing- completetitle: StatusWebhook:description: An enumeration.enum:- stockUpdatetitle: WebhookWebhooksSubscribePostRequest:properties:url:title: Urltype: stringwebhook:$ref: '#/components/schemas/Webhook'title: WebhooksSubscribePostRequesttype: objectpaths:/authenticate:post:description: Authenticate with the API by providing a username and password.operationId: authenticate_authenticate_postrequestBody:content:application/json:schema:$ref: '#/components/schemas/AuthenticatePostRequest'required: trueresponses:'200':content:application/json:schema:$ref: '#/components/schemas/AuthenticatePostResponse'description: Successful Response5XX:content:application/json:schema:$ref: '#/components/schemas/APIError'description: Server Errordefault:content:application/json:schema:$ref: '#/components/schemas/Error'description: Default Responsesummary: Authenticatetags:- authentication/drink/{name}:get:description: Get a drink.operationId: get_drink_drink__name__getparameters:- in: pathname: namerequired: trueschema:title: Nametype: stringresponses:'200':content:application/json:schema:$ref: '#/components/schemas/Drink'description: Successful Response5XX:content:application/json:schema:$ref: '#/components/schemas/APIError'description: Server Errordefault:content:application/json:schema:$ref: '#/components/schemas/Error'description: Default Responsesummary: Get Drinktags:- drinks/drinks:get:description: Get a list of drinks.operationId: list_drinks_drinks_getparameters:- in: queryname: typerequired: falseschema:$ref: '#/components/schemas/DrinkType'responses:'200':content:application/json:schema:items:$ref: '#/components/schemas/Drink'title: Response List Drinks Drinks Gettype: arraydescription: Successful Response5XX:content:application/json:schema:$ref: '#/components/schemas/APIError'description: Server Errordefault:content:application/json:schema:$ref: '#/components/schemas/Error'description: Default Responsesummary: List Drinkstags:- drinks/ingredients:get:description: Get a list of ingredients.operationId: list_ingredients_ingredients_getrequestBody:content:application/json:schema:items:type: stringtitle: Ingredientstype: arrayresponses:'200':content:application/json:schema:items:$ref: '#/components/schemas/Ingredient'title: Response List Ingredients Ingredients Gettype: arraydescription: Successful Response5XX:content:application/json:schema:$ref: '#/components/schemas/APIError'description: Server Errordefault:content:application/json:schema:$ref: '#/components/schemas/Error'description: Default Responsesummary: List Ingredientstags:- ingredients/order:post:description: Create an order.operationId: create_order_order_postparameters:- in: queryname: callback_urlrequired: falseschema:title: Callback Urltype: stringrequestBody:content:application/json:schema:items:$ref: '#/components/schemas/Order'title: Bodytype: arrayrequired: trueresponses:'200':content:application/json:schema:$ref: '#/components/schemas/Order'description: Successful Response5XX:content:application/json:schema:$ref: '#/components/schemas/APIError'description: Server Errordefault:content:application/json:schema:$ref: '#/components/schemas/Error'description: Default Responsesummary: Create Ordertags:- orders/webhooks/subscribe:post:description: Subscribe to webhooks.operationId: subscribe_to_webhooks_webhooks_subscribe_postrequestBody:content:application/json:schema:items:$ref: '#/components/schemas/WebhooksSubscribePostRequest'title: Bodytype: arrayrequired: trueresponses:'200':content:application/json:schema: {}description: Successful Response5XX:content:application/json:schema:$ref: '#/components/schemas/APIError'description: Server Errordefault:content:application/json:schema:$ref: '#/components/schemas/Error'description: Default Responsesummary: Subscribe To Webhookstags:- configuration
Steps to Adapt Swagger 2.0 URL Definitions to OpenAPI 3.x
-
Identify Swagger 2.0 URL Components: Extract the
host
,basePath
, andschemes
values from your Swagger 2.0 document. -
Create the servers array: Initiate a
servers
array in your OpenAPI 3.x document. -
Translate URL Components: For each scheme in your Swagger 2.0 document, create a
server
object in the OpenAPI 3.xservers
array. Combine thescheme
,host
, andbasePath
to form theurl
field of each server object.
For example, if your Swagger 2.0 document contains the following URL components:
swagger: "2.0"host: api.example.combasePath: /v1schemes:- https- http
You would create the following servers
array in your OpenAPI 3.x document:
openapi: 3.1.0servers:- 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:
-
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. -
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.
-
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. -
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. -
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. -
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
-
Defining variables: Variables in OpenAPI are defined within the
servers
array. Each server object can have avariables
field, which is an object itself. This object contains keys representing variable names, each with its set of properties. -
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.
-
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
-
Name variables clearly: Choose clear and descriptive names for your variables. Names like
environment
,region
, orversion
can be more intuitive for users to understand and use. -
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.
-
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 likeproduction
,staging
, anddevelopment
are valid. -
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.
-
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: 3.1.0servers:- url: https://{environment}.api.example.com/v1description: API server - selectable environmentvariables:environment:default: productionenum:- production- staging- developmentdescription: 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
-
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.
-
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.comdescription: Dedicated server for file uploadspost: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:
openapi: 3.0.0paths:/v1/files:post:operationId: PostFilesservers:- 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.
openapi: 3.0.0servers:- url: https://{subdomain}.{site}variables:site:default: datadoghq.comdescription: The regional site for Datadog customers.enum:- datadoghq.com- us3.datadoghq.com- us5.datadoghq.com- ap1.datadoghq.com- datadoghq.eu- ddog-gov.comsubdomain:default: apidescription: The subdomain where the API is deployed.- url: "{protocol}://{name}"variables:name:default: api.datadoghq.comdescription: Full site DNS name.protocol:default: httpsdescription: The protocol for accessing the API.- url: https://{subdomain}.{site}variables:site:default: datadoghq.comdescription: Any Datadog deployment.subdomain:default: apidescription: The subdomain where the API is deployed.paths:/api/v2/logs:post:operationId: SubmitLogservers:- url: https://{subdomain}.{site}variables:site:default: datadoghq.comdescription: The regional site for customers.enum:- datadoghq.com- us3.datadoghq.com- us5.datadoghq.com- ap1.datadoghq.com- datadoghq.eu- ddog-gov.comsubdomain:default: http-intake.logsdescription: The subdomain where the API is deployed.- url: "{protocol}://{name}"variables:name:default: http-intake.logs.datadoghq.comdescription: Full site DNS name.protocol:default: httpsdescription: The protocol for accessing the API.- url: https://{subdomain}.{site}variables:site:default: datadoghq.comdescription: Any Datadog deployment.subdomain:default: http-intake.logsdescription: 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:
- OpenAPI Specification: The official OpenAPI Specification (opens in a new tab) documentation provides comprehensive guidance on server definitions and other aspects of OpenAPI.
- 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.
- 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.
- Speakeasy documentation: Our documentation on how to configure your servers in OpenAPI while creating SDKs with Speakeasy.