Consistent API URLs with OpenAPI and Style Guides

Nauman Ali
by Nauman Ali on May 31, 2022 8 min read

This is the second blog in our Style Guide Rulebook Series, for the first part, go here.

REST APIs are designed around resources, which are any kind of object, data, or service that can be accessed by the client. A resource has an identifier, which is a URL that uniquely identifies that resource. Having consistent and robust URLs – will prove one of the best design decisions to build a good developer experience.

A URL consists of the following parts:

url parts

Here are some style guide rules that you can create and enforce using spectral to ensure best practices and consistency within API URLs while designing APIs.

Protocol

Always use HTTPS

APIs must use HTTPS protocol unless being called in a local environment i.e. localhost

Example Rule (OAS3.x)


oas3-always-use-https:
	given: $.servers[*].url
	then:
		function: pattern
		functionOptions:
			match:   http://localhost)|(https).*
	message: Servers must use the HTTPS protocol except when using localhost
	formats: oas3
	severity: error


oas2-always-use-https:
	given: $.schemes.*
	then:
		function: enummeration
		functionOptions:
	    values:
		    - https
	message: Servers must use the HTTPS protocol
	formats: oas2
	severity: warn

Host

Must be lowercase

URLs are case sensitive but it is bad practice for them to differ based only on capitalization. This can cause a lot of confusion among developers. It is usually expected for URLs to be lowercase.


server-lowercase:
	given: $.servers[*].url
	then:
		function: pattern
		functionOptions:
			match: ^[^A-Z]*$
	message: Server URL must be lowercase
	formats: oas3
	severity: error

Structure

There are multiple conventions that organizations follow to structure their hosts. Some organizations might use *{domain}/api* while others might go for a *api.{name}.com* structure.

Popular URL conventions

url examples

Either way, whatever convention you decide on, it is important to stick to it across your API suite. For example, if you want to use `/api` convention, make sure all APIs have that in their hostnames defined in the OpenAPI.


server-has-api:
	given: $.servers[*].url
	then:
		function: pattern
		functionOptions:
	# change match to notMatch if looking to never have /api
			match: ^/api
	message: Server must have /api
	formats: oas3
	severity: error

In case you are going with another convention, it would be a good idea to never have `/api` in the path, in which case change the `Match` to `notMatch`

Versioning

There can be multiple versioning strategies for APIs. Some organizations might use URL versioning while others might go for path-level versioning.

Popular versioning strategies

url examples

URL Versioning

One common way to version is on the basepath level e.g. api.example.com/v1. If you go for this versioning strategy, make sure that all basepaths have version numbers in them.


server-version:
	given: $.servers[*].url
	then:
		function: pattern
		functionOptions:
	# use version[1-9] if looking for /version1 instead of /v1
	# change match to notMatch if looking to never have version on host level
			match: ^.*/v[1-9]
	message: Server must have end with a version
	formats: oas3
	severity: error

Moreover, it’s a good idea to just have major versions within your base path. You can enforce that with the following configuration.


server-major-version-only:
	given: $.servers[*].url
	then:
		function: pattern
		functionOptions:
			notMatch: .
	message: Server should only have major versions
	formats: oas3
	severity: error

Path Versioning

If you are using path versioning, make sure that all paths have version numbers in them.


path-casing:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
	# For underscore_case: ^/([a-z0-9]+(_[a-z0-9]+)*)?(/[a-z0-9]+(_[a-z0-9]+)*|/{.+})*$
	# For camelCase case: ^/([a-z][a-zA-Z0-9]+)?(/[a-z][a-zA-Z0-9]+|/{[a-z][a-zA-Z0-9]+})*$
			match: ^/([a-z0-9]+(-[a-z0-9]+)*)?(/[a-z0-9]+(-[a-z0-9]+)*|/{.+})*$
	message: Paths must be kebab-case
	severity: error

Path

Must be kebab-case

Paths are usually one of the following across popular APIs: kebab-case, underscore_case, or camelCase. It is advisable to use kebab-case, as It typically looks clearer and hence more user-friendly than using underscores (_).

Popular URL casing conventions

url examples 3

Again irrespective of the casing you choose it’s important to stay consistent.

Screen Shot 2022-06-01 at 10.54.40 AM

In general, it’s usually preferred to use plural nouns for resources unless a plural doesn’t exist for the noun.

used by


resource-names-plural:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
			match: ^((/vd+)*((/[w+-]*s)(/{.*})*)*)$
	message: Resource names should generally be plural
	# Don’t create this as an error, but rather a warning or info level rule,
	# considering there are exceptions for singleton resources.
	severity: warn

Must not have a trailing slash

*/users* and */users/* are considered to be separate paths but it is bad practice for them to differ based only on a trailing slash. This can cause confusion among users of your API.

It is usually preferred to not have a trailing slash.

used by

Again, either way, be consistent by creating a rule.


paths-no-trailing-slash:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
			notMatch: /$
	message: Paths must not end with a trailing slash
	severity: error

Don’t use file extensions in Paths

The use of file extensions is simply seen as unnecessary in URLs. This is because it can cause some issues for the end-users if you change the file type of the results. Hence, it might add unnecessary complexity that you would like to avoid.


paths-no-trailing-slash:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
	# Feel free to add other extensions to this list
			notMatch: (JSON|json|XML|xml)
	message: Paths must not have file extensions
	severity: error

Don’t add HTTP methods to paths

When you design a REST API, you don’t usually need to mention terms like `get`, `delete` and so on in your `paths`, because this information is conveyed by the HTTP method.


paths-no-trailing-slash:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
	# Feel free to add other verbs to this list
			notMatch: (GET|PUT|POST|DELETE|LIST|CREATE|REMOVE)
	message: Paths must not have HTTP verbs in them
	severity: error



paths-version-number:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
	# Change match to notMatch if you're not looking for versions on the path level
			match:  (/v[1-9]|/version[1-9])
	message: Paths must have a version in them
	severity: error

Avoid Special Characters

URLs can only be sent and received using the ASCII character set, your API URLs should contain only ASCII characters. However, that does not mean you can just use all the characters. Special characters like %20 should not be used in paths.


paths-avoid-special-characters:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
	# Change match to notMatch if you're not looking for versions on the path level
			notMatch:  ^[$&+,;=?@]*$
	message: Avoid using special characters in paths
	severity: warn

Use Nouns for Resource Names

Using nouns for resource names leads to easier-to-use APIs. This can be done via custom functions in Spectral which we’ll cover in another article in this series.

Parameters

Don’t include query params as part of the URI

Query parameters in OpenAPI are defined separately. Adding them to the path itself should be avoided.


paths-no-query-params:
	given: $.paths
	then:
	# Targeting the key of paths object i.e. /products/{productId}
		field: @key
		function: pattern
		functionOptions:
			notMatch: ?
	message: Paths should not have query parameters in them. They should be defined separately in the OpenAPI.
	severity: warn

Path Parameter Casing

Path parameters are usually underscore_case or camelCase.

Popular path parameter casing conventions

Used by


path-parameters-camelcase:
	given: $..parameters[?(@.in == 'path')].name
	then:
		function: casing
		functionOptions:
			type: camel
			disallowDigits: true
	message: Path parameters should be camelCase and not contain digits
	severity: error

Query Parameter Casing

Query parameters are usually underscore_case or camelCase.

Popular query parameter casing conventions

used by


query-parameters-camelcase:
	given: $..parameters[?(@.in == 'query')].name
	then:
		function: casing
		functionOptions:
			type: camel
			disallowDigits: true
	message: Query parameters should be camelCase and not contain digits
	severity: error

It is usually a good idea to have both query and path parameters follow the same conventions.

Path Parameters should not be defined on the operation level

Path parameters should be defined on the path level instead of the operation level.


path-parameters-camelcase:
	given: $.paths[*][*].parameters[?(@.in == 'path')]
	then:
		function: falsy
	message: Path parameters should be defined on the path level instead of the operation level.
	severity: warn

Mandatory Paths

Always define a status path

APIs MUST have a status path defined (/status), to easily get started with an API and ping for status updates.

status-path-defined:
	given: $.paths[*][*]
	then:
		function: truthy
	message: APIs MUST have a status path defined (`/status`)
	severity: warn

With not much standardization and so many conventions to worry about when building REST APIs, a core set of guidelines that everybody agrees on is necessary. Convert them into style guide rules that can then be enforced as part of the design and governance process. A combination of these rules helps you create consistent REST API URLs, which leads to an enhanced developer experience. After all, over 50% of organizations use or plan on using internal style guidelines this year, and standardization is the #1 challenge teams are looking to overcome this year, according to the State of API report (2022). This is simply another way you can implement consistency and standardization in your own API program!

Get started with these rules in this sample project.

For more information on style guides, check out our feature blog or our open-source linting tool, Spectral. If you have other ideas on URL conventions and rules, submit an idea on our product roadmap.

Share this post

Stoplight to Join SmartBear!

As a part of SmartBear, we are excited to offer a world-class API solution for all developers' needs.

Learn More
The blog CTA goes here! If you don't need a CTA, make sure you turn the "Show CTA Module" option off.

Take a listen to The API Intersection.

Hear from industry experts about how to use APIs and save time, save money, and grow your business.

Listen Now