URIs
Overview
URI path design is an important part of every API. Paths SHOULD be easy to read and MAY reflect the hierarchy of underlying models. Paths SHOULD NOT end with a trailing /
; if a client appends a trailing /
, the server
SHOULD respond with a 301
status code along with a Location
header containing the correct URI.
Version
The first segment of an API's path MUST be the major version of the API, prefixed with a lowercase
v
.
Construction
Following the version segment, each path segment MUST be either a resource type or a resource identifier. If denoting a collection of resources (e.g., servers
in /v2/servers
), or serving as a prefix to an identifier
(e.g., servers
in /v2/servers/123
), resource types MUST be plural. Resource type names MUST be lower snake case. If a resource type segment contains uppercase characters, the server SHOULD respond with a 404
status and appropriate error response model.
A path MUST NOT have two concurrent identifiers. Removing one or more segments from the end of a path SHOULD yield a valid URI. This means that the existence of
/v2/servers/123/hardware_components
implies that /v2/servers/123
, /v2/servers
, etc., are valid.
Path hierarchies
When one resource is a child of another resource, its path MAY reflect this hierarchical relationship. However, in many cases, it may be deemed more appropriate for subordinate resources to have their own top-level collections. Moreover, a resource's
permanent location (represented in its
href
property) SHOULD be under the shallowest collection where it is represented.
Consider the following path representing ticket 456
, belonging to user 123
:
/v2/users/123/tickets/456
. If it is acceptable for tickets to be accessible only via the users that own them, this may be a reasonable design. But if there is ever a need for a collection of
all tickets, a collection would exist at, for example, /v2/tickets
and an individual ticket at
/v2/tickets/456
.
Additionally, if a collection of all tickets exists, it may be more practical for the collection of tickets belonging to user 123
to exist at /v2/tickets?user=123
than at /v2/users/123/tickets
. It is not
forbidden, however, for a server to implement such collections redundantly.
Contraints
Services MUST have a documented and enforced service-wide length limit for URIs and this limit SHOULD be 8000 bytes[1]. Requests with URIs longer than the limit MUST be rejected
prior to any processing of the payload with a 414 URI Too Long
status code and appropriate error response model.
Path parameters
While not a part of HTTP per se, path parameters are the way OpenAPI represents segments in an operation's path that contain arguments to be provided by a client at request time.
A path parameter SHOULD only be used for a resource identifier, another form of guaranteed-unique handle[2] (such as an immutable name) or a "minimally represented" resource that can be entirely encoded as a single string (such as a tag).
A path parameter MUST NOT be used to provide a collection filter, pagination control, argument for a custom operation, access token, or supplementary directive for the creation or mutation of a resource.
Path parameter names
If a property in the response schema for an operation reflects the same value provided in the final path parameter containing an identifier (or other unique resource handle), that property and that parameter MUST use the same name.
For example, if the operation to retrieve a farm resource from its identifier returns:
{
"id": "0d45edaa-456f-48f2-abff-c01fd6b7530f",
"name": "Moo-Cow Milk Farm"
}
Then the OpenAPI path parameter for the farm's identifier MUST be named "id":
GET /farms/{id}
Conversely, a path parameter MUST NOT have the same name as a top-level property in the operation's response body that represents a different value. Additionally, a path parameter MUST NOT have the same name as any top-level property in the operation's request body.
The path parameters used to identify a resource and any of its parent resources MUST use consistent names across the resource type's standard operations for creating, retrieving, mutating, deleting, and listing resources of that type.
For example, if /farms/{farm_id}/barns/{id}
is used to retrieve, mutate, or delete a barn, the path used to list or create barns MUST be /farms/{farm_id}/barns
and not /farms/{id}/barns
.
As in the example above, and where otherwise sufficient, the singular form of the prior path segment SHOULD be used to disambiguate a parent resource's identifier from its child's identifier. For example, /farms/{farm_id}/barns/{barn_id}/cows/{id}
SHOULD be used instead of
/farms/{farm_id}/barns/{farm_barn_id}/cows/{id}
.
Children without identifiers and custom operations
Where there is no ambiguity, path parameters SHOULD NOT be qualified. For example, if a book resource type allows genres wholly represented by strings to be added with the path
PUT /books/{id}/genres/{genre}
where {genre}
is a string such as "sci-fi," and there is no request- or response-body representation of a genre and no genre identifier, the book identifier path parameter
SHOULD remain unqualified as {id}
.
Similarly, if a resource supports a custom operation modeled as an appended verb in a path segment, such as POST /servers/{id}/reboot
, the server identifier path parameter SHOULD remain unqualified as {id}
.
Defining path parameters
In an OpenAPI definition, path parameters SHOULD be defined in such a way as to maximize reuse and minimize duplication and potential inconsistency. To this end, path parameters MUST be enumerated in the Path Item object and not the Operation object. Further, path parameters SHOULD be defined as Components and referenced in Path Item objects. Finally, schemas used by parameters SHOULD also be defined as separate Components and referenced in Parameter objects.
Example
The following example demonstrates how parameters can be defined in an OpenAPI definition to maximize reuse.
paths:
'/farms/{id}':
parameters:
- $ref: '#/components/parameters/FarmId'
get:
operationId: get_farm
...
patch:
operationId: update_farm
...
'/farms/{farm_id}/barns':
parameters:
- $ref: '#/components/parameters/FarmIdQualified'
get:
operationId: list_farm_barns
...
post:
operationId: create_farm_barn
...
'/farms/{farm_id}/barns/{id}':
parameters:
- $ref: '#/components/parameters/FarmIdQualified'
- $ref: '#/components/parameters/FarmBarnId'
get:
operationId: get_farm_barn
...
patch:
operationId: update_farm_barn
...
components:
parameters:
FarmId:
name: id
in: path
required: true
description: The identifier for a farm
schema:
$ref: '#/components/schemas/Identifier'
FarmIdQualified:
name: farm_id
in: path
required: true
description: The identifier for a farm
schema:
$ref: '#/components/schemas/Identifier'
FarmBarnId:
name: id
in: path
required: true
description: The identifier for a barn on a farm
schema:
$ref: '#/components/schemas/Identifier'
schemas:
Identifier:
type: string
format: identifier
pattern: '^[-0-9a-z]+$'
Query parameters
Contraints
Each query parameter supported for an operation MUST have a documented and enforced maximum length and the sum of all query parameter maximum lengths (along with the names and control characters &
and =
for each
parameter) for a single operation SHOULD be less than 7000 bytes[3].
Unrecognized and invalid parameters
To prevent dangerous client bugs and backward-compatibility hazards, unrecognized query parameters SHOULD and invalid parameter values MUST result in
a 400
status code and appropriate error response model.
Case insensitivity
Query parameter names SHOULD NOT be case-normalized to support case insensitivity; a parameter that does not match the case of a defined parameter but otherwise matches its name SHOULD be treated as any other extraneous input[4].
However, parameter name case normalization MAY be supported for backward compatibility with existing clients.
Parameter duplication
Requests that provide a query string with duplicate single-value[5] query parameters of the same name and differing values[6] MUST result in a 400
status and appropriate error response model. For backward compatibility with existing clients, query strings containing duplicate query parameters of the same name and with the same value MAY be supported
[7].
If a service supports parameter name case insensitivity, parameter names MUST be normalized prior to validating uniqueness.
Support for array input in query parameters SHOULD use comma-separated values within a single parameter (for example, foo=1,2,3
) instead of duplicated[8] parameters
(
foo=1&foo=2&foo=3
).
-
RFC 7230 recommends supporting a minimum URI length of 8000 octets. Given that various tools written to this standard may support no more than this recommended minimum, this handbook recommends a limit of exactly 8000 bytes. ↩︎
-
The recommended maximum URI length is 8000 bytes; leaving 1000 bytes for imaginatively long fully qualified domain name and path segments, it should be impossible to craft a URI with entirely valid parameters (and no padding) that exceeds the URI length limit. This allows services to reliably return user-crafted collection URIs with appended pagination tokens. ↩︎
-
Case normalization is often an error-prone process, however simple it may seem. One problem is that different standard libraries may not agree on the lowercase-equivalent value for a particular string. For example,
"İstanbul".ToLowerCase()
in JavaScript yieldsi̇stanbul
(note the two dots over the first character), butstrings.ToLower("İstanbul")
in Go is more aware of locale-specific rules and yieldsistanbul
. If the code that validates and the code that actually uses a particular parameter disagree on the normalization of its name, it could lead to a bug that could be exploited to validate one value and use another. ↩︎ -
That is, query parameters that do not support array input. ↩︎
-
Silent support for ambiguously duplicated query parameters increases the risk that a malicious client could craft a request that bypasses critical authorization or validation checks. ↩︎
-
That is, exactly duplicated parameters can be treated as if only one parameter with the duplicated name and value was provided. ↩︎
-
While this kind of parameter duplication is not ambiguous per se, tooling support for constructing and parsing such query strings is uneven. ↩︎