Expand change history
Date | Notes |
---|---|
2024-Jan-17 | Added guidelines on returning string offsets & lengths |
2023-May-12 | Explain service response for missing/unsupported api-version |
2023-Apr-21 | Update/clarify guidelines on POST method repeatability |
2023-Apr-07 | Update/clarify guidelines on polymorphism |
2022-Sep-07 | Updated URL guidelines for DNS Done Right |
2022-Jul-15 | Update guidance on long-running operations |
2022-May-11 | Drop guidance on version discovery |
2022-Mar-29 | Add guidelines about using durations |
2022-Mar-25 | Update guideline for date values in headers to follow RFC 7231 |
2022-Feb-01 | Updated error guidance |
2021-Sep-11 | Add long-running operations guidance |
2021-Aug-06 | Updated Azure REST Guidelines per Azure API Stewardship Board. |
2020-Jul-31 | Added service advice for initial versions |
2020-Mar-31 | 1st public release of the Azure REST API Guidelines |
These guidelines apply to Azure service teams implementing data plane APIs. They offer prescriptive guidance that Azure service teams MUST follow ensuring that customers have a great experience by designing APIs meeting these goals:
- Developer friendly via consistent patterns & web standards (HTTP, REST, JSON)
- Efficient & cost-effective
- Work well with SDKs in many programming languages
- Customers can create fault-tolerant apps by supporting retries/idempotency/optimistic concurrency
- Sustainable & versionable via clear API contracts with 2 requirements:
- Customer workloads must never break due to a service change
- Customers can adopt a version without requiring code changes Technology and software is constantly changing and evolving, and as such, this is intended to be a living document. Open an issue to suggest a change or propose a new idea. Please read the Considerations for Service Design for an introduction to the topic of API design for Azure services. For an existing GA'd service, don't change/break its existing API; instead, leverage these concepts for future APIs while prioritizing consistency within your existing service.
Note: If you are creating a management plane (ARM) API, please refer to the Azure Resource Manager Resource Provider Contract.
This document offers prescriptive guidance labeled as follows:
✅ DO adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation.
☑️ YOU SHOULD adopt this pattern. If not following this advice, you MUST disclose your reason during the Azure HTTP/REST Stewardship Board review.
✔️ YOU MAY consider this pattern if appropriate to your situation. No notification to the Azure HTTP/REST Stewardship Board is required.
⛔ DO NOT adopt this pattern. If you feel you need an exception, contact the Azure HTTP/REST Stewardship Board prior to implementation.
If you feel you need an exception, or need clarity based on your situation, please contact the Azure HTTP/REST Stewardship Board prior to release of your API.
The Microsoft Azure Cloud platform exposes its APIs through the core building blocks of the Internet; namely HTTP, REST, and JSON. This section provides you with a general understanding of how these technologies should be applied when creating your service.
Azure services must adhere to the HTTP specification, RFC7231. This section further refines and constrains how service implementors should apply the constructs defined in the HTTP specification. It is therefore, important that you have a firm understanding of the following concepts:
- Uniform Resource Locators (URLs)
- HTTP Request / Response Pattern
- HTTP Query Parameters and Header Values
A Uniform Resource Locator (URL) is how developers access the resources of your service. Ultimately, URLs are how developers form a cognitive model of your service's resources.
✅ DO use this URL pattern:
https://<tenant>.<region>.<service>.<cloud>/<service-root>/<resource-collection>/<resource-id>
Where:
Field | Description |
---|---|
tenant | Regionally-unique ID representing a tenant (used for isolation, billing, quota enforcement, lifetime of resources, etc.) |
region | Identifies the tenant's selected region. This region string MUST match one of the strings in the "Name" column returned from running this Azure CLI's "az account list-locations -o table" |
service | Name of the service (ex: blobstore, servicebus, directory, or management) |
cloud | Cloud domain name, e.g. azure.net (see Azure CLI's "az cloud list") |
service‑root | Service-specific path (ex: blobcontainer, myqueue) |
resource‑collection | Name of the collection, unabbreviated, pluralized |
resource‑id | Id of resource within the resource-collection. This MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. |
✅ DO use kebab-casing (preferred) or camel-casing for URL path segments. If the segment refers to a JSON field, use camel casing.
✅ DO return 414-URI Too Long
if a URL exceeds 2083 characters
✅ DO treat service-defined URL path segments as case-sensitive. If the passed-in case doesn't match what the service expects, the request MUST fail with a 404-Not found
HTTP return code.
Some customer-provided path segment values may be compared case-insensitivity if the abstraction they represent is normally compared with case-insensitivity. For example, a UUID path segment of 'c55f6b35-05f6-42da-8321-2af5099bd2a2' should be treated identical to 'C55F6B35-05F6-42DA-8321-2AF5099BD2A2'
✅ DO ensure proper casing when returning a URL in an HTTP response header value or inside a JSON response body
✅ DO restrict the characters in service-defined path segments to 0-9 A-Z a-z - . _ ~
, with :
allowed only as described below to designate an action operation.
☑️ YOU SHOULD restrict the characters allowed in user-specified path segments (i.e. path parameters values) to 0-9 A-Z a-z - . _ ~
(do not allow :
).
☑️ YOU SHOULD keep URLs readable; if possible, avoid UUIDs & %-encoding (ex: Cádiz is %-encoded as C%C3%A1diz)
✔️ YOU MAY use these other characters in the URL path but they will likely require %-encoding [RFC 3986]: / ? # [ ] @ ! $ & ' ( ) * + , ; =
✔️ YOU MAY support a direct endpoint URL for performance/routing:
https://<tenant>-<service-root>.<service>.<cloud>/...
Examples:
- Request URL:
https://blobstore.azure.net/contoso.com/account1/container1/blob2
- Response header (RFC2557):
content-location : https://contoso-dot-com-account1.blobstore.azure.net/container1/blob2
- GUID format:
https://00000000-0000-0000-C000-000000000046-account1.blobstore.azure.net/container1/blob2
✅ DO return URLs in response headers/bodies in a consistent form regardless of the URL used to reach the resource. Either always a UUID for <tenant>
or always a single verified domain.
✔️ YOU MAY use URLs as values
https://api.contoso.com/items?url=https://resources.contoso.com/shoes/fancy
The HTTP Request / Response pattern dictates how your API behaves. For example: POST methods that create resources must be idempotent, GET method results may be cached, the If-Modified and ETag headers offer optimistic concurrency. The URL of a service, along with its request/response bodies, establishes the overall contract that developers have with your service. As a service provider, how you manage the overall request / response pattern should be one of the first implementation decisions you make.
Cloud applications embrace failure. Therefore, to enable customers to write fault-tolerant applications, all service operations (including POST) must be idempotent. Implementing services in an idempotent manner, with an "exactly once" semantic, enables developers to retry requests without the risk of unintended consequences.
✅ DO ensure that all HTTP methods are idempotent.
☑️ YOU SHOULD use PUT or PATCH to create a resource as these HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent.
✔️ YOU MAY use POST to create a resource but you must make it idempotent and, of course, the response MUST return the URL of the created resource with a 201-Created. One way to make POST idempotent is to use the Repeatability-Request-ID & Repeatability-First-Sent headers (See Repeatability of requests).
✅ DO adhere to the return codes in the following table when the method completes synchronously and is successful:
Method | Description | Response Status Code |
---|---|---|
PATCH | Create/Modify the resource with JSON Merge Patch | 200-OK , 201-Created |
PUT | Create/Replace the whole resource | 200-OK , 201-Created |
POST | Create new resource (ID set by service) | 201-Created with URL of created resource |
POST | Action | 200-OK |
GET | Read (i.e. list) a resource collection | 200-OK |
GET | Read the resource | 200-OK |
DELETE | Remove the resource | 204-No Content ; avoid 404-Not Found |
✅ DO return status code 202-Accepted
and follow the guidance in Long-Running Operations & Jobs when a PUT, POST, or DELETE method completes asynchronously.
✅ DO treat method names as case sensitive and should always be in uppercase
✅ DO return the state of the resource after a PUT, PATCH, POST, or GET operation with a 200-OK
or 201-Created
.
✅ DO return a 204-No Content
without a resource/body for a DELETE operation (even if the URL identifies a resource that does not exist; do not return 404-Not Found
)
✅ DO return a 200-OK
from a POST Action. Include a body in the response, even if it has not properties, to allow properties to be added in the future if needed.
✅ DO return a 403-Forbidden
when the user does not have access to the resource unless this would leak information about the existence of the resource that should not be revealed for security/privacy reasons, in which case the response should be 404-Not Found
. [Rationale: a 403-Forbidden
is easier to debug for customers, but should not be used if even admitting the existence of things could potentially leak customer secrets.]
✅ DO support caching and optimistic concurrency by honoring the the If-Match
, If-None-Match
, if-modified-since, and if-unmodified-since request headers and by returning the ETag and last-modified response headers
✅ DO use camel case for query parameter names.
Note: Certain legacy query parameter names use kebab-casing and are allowed only for backwards compatibility.
Because information in the service URL, as well as the request / response, are strings, there must be a predictable, well-defined scheme to convert strings to their corresponding values.
✅ DO validate all query parameter and request header values and fail the operation with 400-Bad Request
if any value fails validation. Return an error response as described in the Handling Errors section indicating what is wrong so customer can diagnose the issue and fix it themselves.
✅ DO use the following table when translating strings:
Data type | Document that string must be |
---|---|
Boolean | true / false (all lowercase) |
Integer | -253+1 to +253-1 (for consistency with JSON limits on integers RFC8259) |
Float | IEEE-754 binary64 |
String | (Un)quoted?, max length, legal characters, case-sensitive, multiple delimiter |
UUID | 123e4567-e89b-12d3-a456-426614174000 (no {}s, hyphens, case-insensitive) RFC4122 |
Date/Time (Header) | Sun, 06 Nov 1994 08:49:37 GMT RFC7231, Section 7.1.1.1 |
Date/Time (Query parameter) | YYYY-MM-DDTHH:mm:ss.sssZ (with at most 3 digits of fractional seconds) RFC3339 |
Byte array | Base-64 encoded, max length |
Array | One of a) a comma-separated list of values (preferred), or b) separate name=value parameter instances for each value of the array |
The table below lists the headers most used by Azure services:
Header Key | Applies to | Example |
---|---|---|
authorization | Request | Bearer eyJ0...Xd6j (Support Azure Active Directory) |
x-ms-useragent | Request | (see Distributed Tracing & Telemetry) |
traceparent | Request | (see Distributed Tracing & Telemetry) |
tracecontext | Request | (see Distributed Tracing & Telemetry) |
accept | Request | application/json |
If-Match | Request | "67ab43" or * (no quotes) (see Conditional Requests) |
If-None-Match | Request | "67ab43" or * (no quotes) (see Conditional Requests) |
If-Modified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see Conditional Requests) |
If-Unmodified-Since | Request | Sun, 06 Nov 1994 08:49:37 GMT (see Conditional Requests) |
date | Both | Sun, 06 Nov 1994 08:49:37 GMT (see RFC7231, Section 7.1.1.2) |
content-type | Both | application/merge-patch+json |
content-length | Both | 1024 |
x-ms-request-id | Response | 4227cdc5-9f48-4e84-921a-10967cb785a0 |
ETag | Response | "67ab43" (see Conditional Requests) |
last-modified | Response | Sun, 06 Nov 1994 08:49:37 GMT |
x-ms-error-code | Response | (see Handling Errors) |
azure-deprecating | Response | (see Deprecating Behavior Notification) |
retry-after | Response | 180 (see RFC 7231, Section 7.1.3) |
✅ DO support all headers shown in italics
✅ DO specify headers using kebab-casing
✅ DO compare request header names using case-insensitivity
✅ DO compare request header values using case-sensitivity if the header name requires it
✅ DO accept date values in headers in HTTP-Date format and return date values in headers in the IMF-fixdate format as defined in RFC7231, Section 7.1.1.1, e.g. "Sun, 06 Nov 1994 08:49:37 GMT".
Note: The RFC 7321 IMF-fixdate format is a "fixed-length and single-zone subset" of the RFC 1123 / RFC 5822 format, which means: a) year must be four digits, b) the seconds component of time is required, and c) the timezone must be GMT.
✅ DO create an opaque value that uniquely identifies the request and return this value in the x-ms-request-id
response header.
Your service should include the x-ms-request-id
value in error logs so that users can submit support requests for specific failures using this value.
⛔ DO NOT fail a request that contains an unrecognized header. Headers may be added by API gateways or middleware and this must be tolerated
⛔ DO NOT use "x-" prefix for custom headers, unless the header already exists in production [RFC 6648].
Additional References
- StackOverflow - Difference between http parameters and http headers
- Standard HTTP Headers
- Why isn't HTTP PUT allowed to do partial updates in a REST API?
REST is an architectural style with broad reach that emphasizes scalability, generality, independent deployment, reduced latency via caching, and security. When applying REST to your API, you define your service’s resources as a collections of items. These are typically the nouns you use in the vocabulary of your service. Your service's URLs determine the hierarchical path developers use to perform CRUD (create, read, update, and delete) operations on resources. Note, it's important to model resource state, not behavior. There are patterns, later in these guidelines, that describe how to invoke behavior on your service. See this article in the Azure Architecture Center for a more detailed discussion of REST API design patterns.
When designing your service, it is important to optimize for the developer using your API.
✅ DO focus heavily on clear & consistent naming
✅ DO ensure your resource paths make sense
✅ DO simplify operations with few required query parameters & JSON fields
✅ DO establish clear contracts for string values
✅ DO use proper response codes/bodies so customer can diagnose their own problems and fix them without contacting Azure support or the service team
✅ DO use the same JSON schema for PUT request/response, PATCH response, GET response, and POST request/response on a given URL path. The PATCH request schema should contain all the same fields with no required fields. This allows one SDK type for input/output operations and enables the response to be passed back in a request.
✅ DO think about your resource's fields and how they are used:
Field Mutability | Service Request's behavior for this field |
---|---|
Create | Service honors field only when creating a resource. Minimize create-only fields so customers don't have to delete & re-create the resource. |
Update | Service honors field when creating or updating a resource |
Read | Service returns this field in a response. If the client passed a read-only field, the service MUST fail the request unless the passed-in value matches the resource's current value |
In addition to the above, a field may be "required" or "optional". A required field is guaranteed to always exist and will typically not become a nullable field in a SDK's data structure. This allows customers to write code without performing a null-check. Because of this, required fields can only be introduced in the 1st version of a service; it is a breaking change to introduce required fields in a later version. In addition, it is a breaking change to remove a required field or make an optional field required or vice versa.
✅ DO make fields simple and maintain a shallow hierarchy.
✅ DO use GET for resource retrieval and return JSON in the response body
✅ DO create and update resources using PATCH [RFC5789] with JSON Merge Patch (RFC7396) request body.
✅ DO use PUT with JSON for wholesale create/replace operations. NOTE: If a v1 client PUTs a resource; any fields introduced in V2+ should be reset to their default values (the equivalent to DELETE followed by PUT).
✅ DO use DELETE to remove a resource.
✅ DO fail an operation with 400-Bad Request
if the request is improperly-formed or if any JSON field name or value is not fully understood by the specific version of the service. Return an error response as described in Handling errors indicating what is wrong so customer can diagnose the issue and fix it themselves.
✔️ YOU MAY return secret fields via POST if absolutely necessary.
⛔ DO NOT return secret fields via GET. For example, do not return administratorPassword
in JSON.
⛔ DO NOT add fields to the JSON if the value is easily computable from other fields to avoid bloating the body.
✅ DO follow the processing below to create/update/replace a resource:
When using this method | if this condition happens | use this response code |
---|---|---|
PATCH/PUT | Any JSON field name/value not known/valid to the api-version | 400-Bad Request |
PATCH/PUT | Any Read field passed (client can't set Read fields) | 400-Bad Request |
If the resource does not exist | ||
PATCH/PUT | Any mandatory Create/Update field missing | 400-Bad Request |
PATCH/PUT | Create resource using Create/Update fields | 201-Created |
If the resource already exists | ||
PATCH | Any Create field doesn't match current value (allows retries) | 409-Conflict |
PATCH | Update resource using Update fields | 200-OK |
PUT | Any mandatory Create/Update field missing | 400-Bad Request |
PUT | Overwrite resource entirely using Create/Update fields | 200-OK |
There are 2 kinds of errors:
- An error where you expect customer code to gracefully recover at runtime
- An error indicating a bug in customer code that is unlikely to be recoverable at runtime; the customer must just fix their code
✅ DO return an x-ms-error-code
response header with a string error code indicating what went wrong.
NOTE: x-ms-error-code
values are part of your API contract (because customer code is likely to do comparisons against them) and cannot change in the future.
✔️ YOU MAY implement the x-ms-error-code
values as an enum with "modelAsString": true
because it's possible add new values over time. In particular, it's only a breaking change if the same conditions result in a different top-level error code.
✅ DO carefully craft unique x-ms-error-code
string values for errors that are recoverable at runtime. Reuse common error codes for usage errors that are not recoverable.
✔️ YOU MAY group common customer code errors into a few x-ms-error-code
string values.
✅ DO ensure that the top-level error's code
value is identical to the x-ms-error-code
header's value.
✅ DO provide a response body with the following structure:
ErrorResponse : Object
Property | Type | Required | Description |
---|---|---|---|
error |
ErrorDetail | ✔ | The top-level error object whose code matches the x-ms-error-code response header |
ErrorDetail : Object
Property | Type | Required | Description |
---|---|---|---|
code |
String | ✔ | One of a server-defined set of error codes. |
message |
String | ✔ | A human-readable representation of the error. |
target |
String | The target of the error. | |
details |
ErrorDetail[] | An array of details about specific errors that led to this reported error. | |
innererror |
InnerError | An object containing more specific information than the current object about the error. | |
additional properties | Additional properties that can be useful when debugging. |
InnerError : Object
Property | Type | Required | Description |
---|---|---|---|
code |
String | A more specific error code than was provided by the containing error. | |
innererror |
InnerError | An object containing more specific information than the current object about the error. |
Example:
{
"error": {
"code": "InvalidPasswordFormat",
"message": "Human-readable description",
"target": "target of error",
"innererror": {
"code": "PasswordTooShort",
"minLength": 6,
}
}
}
✅ DO document the service's top-level error code strings; they are part of the API contract.
✔️ YOU MAY treat the other fields as you wish as they are not considered part of your service's API contract and customers should not take a dependency on them or their value. They exist to help customers self-diagnose issues.
✔️ YOU MAY add additional properties for any data values in your error message so customers don't resort to parsing your error message. For example, an error with "message": "A maximum of 16 keys are allowed per account."
might also add a "maximumKeys": 16
property. This is not part of your API contract and should only be used for diagnosing problems.
Note: Do not use this mechanism to provide information developers need to rely on in code (ex: the error message can give details about why you've been throttled, but the Retry-After
should be what developers rely on to back off).
✅ DO use camel case for all JSON field names. Do not upper-case acronyms; use camel case.
✅ DO treat JSON field names with case-sensitivity.
✅ DO treat JSON field values with case-sensitivity. There may be some exceptions (e.g. GUIDs) but avoid if at all possible.
Services, and the clients that access them, may be written in multiple languages. To ensure interoperability, JSON establishes the "lowest common denominator" type system, which is always sent over the wire as UTF-8 bytes. This system is very simple and consists of three types:
Type | Description |
---|---|
Boolean | true/false (always lowercase) |
Number | Signed floating point (IEEE-754 binary64; int range: -253+1 to +253-1) |
String | Used for everything else |
✅ DO use integers within the acceptable range of JSON number.
✅ DO establish a well-defined contract for the format of strings. For example, determine maximum length, legal characters, case-(in)sensitive comparisons, etc. Where possible, use standard formats, e.g. RFC3339 for date/time.
✅ DO use strings formats that are well-known and easily parsable/formattable by many programming languages, e.g. RFC3339 for date/time.
✅ DO ensure that information exchanged between your service and any client is "round-trippable" across multiple programming languages.
✅ DO use RFC3339 for date/time.
✅ DO use a fixed time interval to express durations e.g., milliseconds, seconds, minutes, days, etc., and include the time unit in the property name e.g., backupTimeInMinutes
or ttlSeconds
.
✔️ YOU MAY use RFC3339 time intervals only when users must be able to specify a time interval that may change from month to month or year to year e.g., "P3M" represents 3 months no matter how many days between the start and end dates, or "P1Y" represents 366 days on a leap year. The value must be round-trippable.
✔️ YOU MAY use JSON objects to group sub-fields together.
✔️ YOU MAY use JSON arrays if maintaining an order of values is required. Avoid arrays in other situations since arrays can be difficult and inefficient to work with, especially with JSON Merge Patch where the entire array needs to be read prior to any operation being applied to it.
☑️ YOU SHOULD use JSON objects instead of arrays whenever possible.
It is common for strings to have an explicit set of values. These are often reflected in the OpenAPI definition as enumerations. These are extremely useful for developer tooling, e.g. code completion, and client library generation.
However, it is not uncommon for the set of values to grow over the life of a service. For this reason, Microsoft's tooling uses the concept of an "extensible enum," which indicates that the set of values should be treated as only a partial list. This indicates to client libraries and customers that values of the enumeration field should be effectively treated as strings and that undocumented value may returned in the future. This enables the set of values to grow over time while ensuring stability in client libraries and customer code.
☑️ YOU SHOULD use extensible enumerations unless you are positive that the symbol set will NEVER change over time.
✅ DO document to customers that new values may appear in the future so that customers write their code today expecting these new values tomorrow.
✔️ YOU MAY return a value for an extensible enum that is not one of the values defined for the api-version specified in the request.
⛔ DO NOT remove values from your enumeration list as this breaks customer code.
Polymorphism types in REST APIs refers to the possibility to use the same property of a request or response to have similar but different shapes. This is commonly expressed as a oneOf
in JsonSchema or OpenAPI. In order to simplify how to determine which specific type a given request or response payload corresponds to, Azure requires the use of an explicit discriminator field.
Note: Polymorphic types can make your service more difficult for nominally typed languages to consume. See the corresponding section in the Considerations for service design for more information.
✅ DO define a discriminator field indicating the kind of the resource and include any kind-specific fields in the body.
Below is an example of JSON for a Rectangle and Circle with a discriminator field named kind
:
Rectangle
{
"kind": "rectangle",
"x": 100,
"y": 50,
"width": 10,
"length": 24,
"fillColor": "Red",
"lineColor": "White",
"subscription": {
"kind": "free"
}
}
Circle
{
"kind": "circle",
"x": 100,
"y": 50,
"radius": 10,
"fillColor": "Green",
"lineColor": "Black",
"subscription": {
"kind": "paid",
"expiration": "2024",
"invoice": "123456"
}
}
Both Rectangle and Circle have common fields: kind
, fillColor
, lineColor
, and subscription
. A Rectangle also has x
, y
, width
, and length
while a Circle has x
, y
, and radius
. The subscription
is a nested polymorphic type. A free
subscription has no additional fields and a paid
subscription has expiration
and invoice
fields.
The Azure Naming Guidelines recommend that the discriminator field be named kind
.
☑️ YOU SHOULD define the discriminator field of a polymorphic type to be an extensible enum.
Updating an array property with JSON merge-patch is not version-resilient if the array contains polymorphic types.
The REST specification is used to model the state of a resource, and is primarily intended to handle CRUD (Create, Read, Update, Delete) operations. However, many services require the ability to perform an action on a resource, e.g. getting the thumbnail of an image or rebooting a VM. It is also sometimes useful to perform an action on a collection.
☑️ YOU SHOULD pattern your URL like this to perform an action on a resource URL Pattern
https://.../<resource-collection>/<resource-id>:<action>?<input parameters>
Example
https://.../users/Bob:grant?access=read
☑️ YOU SHOULD pattern your URL like this to perform an action on a collection URL Pattern
https://.../<resource-collection>:<action>?<input parameters>
Example
https://.../users:grant?access=read
Note: To avoid potential collision of actions and resource ids, you should disallow the use of the ":" character in resource ids.
✅ DO use a POST operation for any action on a resource or collection.
✅ DO support the Repeatability-Request-ID & Repeatability-First-Sent request headers if the action needs to be idempotent if retries occur.
✅ DO return a 200-OK
when the action completes synchronously and successfully.
☑️ YOU SHOULD use a verb as the <action>
component of the path.
⛔ DO NOT use an action operation when the operation behavior could reasonably be defined as one of the standard REST Create, Read, Update, Delete, or List operations.
✅ DO structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources.
☑️ YOU SHOULD support paging today if there is ever a chance in the future that the number of items can grow to be very large.
NOTE: It is a breaking change to add paging in the future
✔️ YOU MAY expose an operation that lists your resources by supporting a GET method with a URL to a resource-collection (as opposed to a resource-id).
Example Response Body
{
"value": [
{ "id": "Item 01", "etag": "\"abc\"", "price": 99.95, "sizes": null },
{ … },
{ … },
{ "id": "Item 99", "etag": "\"def\"", "price": 59.99, "sizes": null }
],
"nextLink": "{opaqueUrl}"
}
✅ DO include the id field and etag field (if supported) for each item as this allows the customer to modify the item in a future operation. Note that the etag field must have escaped quotes embedded within it; for example, ""abc"" or W/""abc"".
✅ DO clearly document that resources may be skipped or duplicated across pages of a paginated collection unless the operation has made special provisions to prevent this (like taking a time-expiring snapshot of the collection).
✅ DO return a nextLink
field with an absolute URL that the client can GET in order to retrieve the next page of the collection.
Note: The service is responsible for performing any URL-encoding required on the nextLink
URL.
✅ DO include any query parameters required by the service in nextLink
, including api-version
.
☑️ YOU SHOULD use value
as the name of the top-level array field unless a more appropriate name is available.
⛔ DO NOT return the nextLink
field at all when returning the last page of the collection.
⛔ DO NOT return the nextLink
field with a value of null.
count
of all objects in the collection as this may be expensive to compute.
✔️ YOU MAY support the following query parameters allowing customers to control the list operation:
Parameter name | Type | Description |
---|---|---|
filter |
string | an expression on the resource type that selects the resources to be returned |
orderby |
string array | a list of expressions that specify the order of the returned resources |
skip |
integer | an offset into the collection of the first resource to be returned |
top |
integer | the maximum number of resources to return from the collection |
maxpagesize |
integer | the maximum number of resources to include in a single response |
select |
string array | a list of field names to be returned for each resource |
expand |
string array | a list of the related resources to be included in line with each resource |
✅ DO return an error if the client specifies any parameter not supported by the service.
✅ DO treat these query parameter names as case-sensitive.
✅ DO apply select
or expand
options after applying all the query options in the table above.
✅ DO apply the query options to the collection in the order shown in the table above.
⛔ DO NOT prefix any of these query parameter names with "$" (the convention in the OData standard).
✔️ YOU MAY support filtering of the results of a list operation with the filter
query parameter.
The value of the filter
query parameter is an expression involving the fields of the resource that produces a Boolean value. This expression is evaluated for each resource in the collection and only items where the expression evaluates to true are included in the response.
✅ DO omit all resources from the collection for which the filter expression evaluates to false or to null, or references properties that are unavailable due to permissions.
Example: return all Products whose Price is less than $10.00
GET https://api.contoso.com/products?filter=price lt 10.00
✔️ YOU MAY support the following operators in filter expressions:
Operator | Description | Example |
---|---|---|
Comparison Operators | ||
eq | Equal | city eq 'Redmond' |
ne | Not equal | city ne 'London' |
gt | Greater than | price gt 20 |
ge | Greater than or equal | price ge 10 |
lt | Less than | price lt 20 |
le | Less than or equal | price le 100 |
Logical Operators | ||
and | Logical and | price le 200 and price gt 3.5 |
or | Logical or | price le 3.5 or price gt 200 |
not | Logical negation | not price le 3.5 |
Grouping Operators | ||
( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 |
✅ DO respond with an error message as defined in the Handling Errors section if a client includes an operator in a filter expression that is not supported by the operation.
✅ DO use the following operator precedence for supported operators when evaluating filter expressions. Operators are listed by category in order of precedence from highest to lowest. Operators in the same category have equal precedence and should be evaluated left to right:
Group | Operator | Description |
---|---|---|
Grouping | ( ) | Precedence grouping |
Unary | not | Logical Negation |
Relational | gt | Greater Than |
ge | Greater than or Equal | |
lt | Less Than | |
le | Less than or Equal | |
Equality | eq | Equal |
ne | Not Equal | |
Conditional AND | and | Logical And |
Conditional OR | or | Logical Or |
✔️ YOU MAY support orderby and filter functions such as concat and contains. For more information, see odata Canonical Functions.
The following examples illustrate the use and semantics of each of the logical operators.
Example: all products with a name equal to 'Milk'
GET https://api.contoso.com/products?filter=name eq 'Milk'
Example: all products with a name not equal to 'Milk'
GET https://api.contoso.com/products?filter=name ne 'Milk'
Example: all products with the name 'Milk' that also have a price less than 2.55:
GET https://api.contoso.com/products?filter=name eq 'Milk' and price lt 2.55
Example: all products that either have the name 'Milk' or have a price less than 2.55:
GET https://api.contoso.com/products?filter=name eq 'Milk' or price lt 2.55
Example: all products that have the name 'Milk' or 'Eggs' and have a price less than 2.55:
GET https://api.contoso.com/products?filter=(name eq 'Milk' or name eq 'Eggs') and price lt 2.55
✔️ YOU MAY support sorting of the results of a list operation with the orderby
query parameter.
NOTE: It is unusual for a service to support orderby
because it is very expensive to implement as it requires sorting the entire large collection before being able to return any results.
The value of the orderby
parameter is a comma-separated list of expressions used to sort the items.
A special case of such an expression is a property path terminating on a primitive property.
Each expression in the orderby
parameter value may include the suffix "asc" for ascending or "desc" for descending, separated from the expression by one or more spaces.
✅ DO sort the collection in ascending order on an expression if "asc" or "desc" is not specified.
✅ DO sort NULL values as "less than" non-NULL values.
✅ DO sort items by the result values of the first expression, and then sort items with the same value for the first expression by the result value of the second expression, and so on.
✅ DO use the inherent sort order for the type of the field. For example, date-time values should be sorted chronologically and not alphabetically.
✅ DO respond with an error message as defined in the Handling Errors section if the client requests sorting by a field that is not supported by the operation.
For example, to return all people sorted by name in ascending order:
GET https://api.contoso.com/people?orderby=name
For example, to return all people sorted by name in descending order and a secondary sort order of hireDate in ascending order.
GET https://api.contoso.com/people?orderby=name desc,hireDate
Sorting MUST compose with filtering such that:
GET https://api.contoso.com/people?filter=name eq 'david'&orderby=hireDate
will return all people whose name is David sorted in ascending order by hireDate.
✅ DO use the same filtering options and sort order for all pages of a paginated list operation response.
✅ DO define the skip
parameter as an integer with a default and minimum value of 0.
✔️ YOU MAY allow clients to pass the skip
query parameter to specify an offset into collection of the first resource to be returned.
✔️ YOU MAY allow clients to pass the top
query parameter to specify the maximum number of resources to return from the collection.
If supporting top
:
:white_check_mark: DO define the top
parameter as an integer with a minimum value of 1. If not specified, top
has a default value of infinity.
✅ DO return the collection's top
number of resources (if available), starting from skip
.
✔️ YOU MAY allow clients to pass the maxpagesize
query parameter to specify the maximum number of resources to include in a single page response.
✅ DO define the maxpagesize
parameter as an optional integer with a default value appropriate for the collection.
✅ DO make clear in documentation of the maxpagesize
parameter that the operation may choose to return fewer resources than the value specified.
Azure services need to change over time. However, when changing a service, there are 2 requirements:
- Already-running customer workloads must not break due to a service change
- Customers can adopt a new service version without requiring any code changes (Of course, the customer must modify code to leverage any new service features.)
NOTE: the Azure Breaking Change Policy has tables (section 5) describing what kinds of changes are considered breaking. Breaking changes are allowable (due to security/compliance/etc.) if approved by the Azure Breaking Change Reviewers but only following ample communication to customers and a lengthy deprecation period.
✅ DO review any API changes with the Azure API Stewardship Board
Clients specify the version of the API to be used in every request to the service, even requests to an Operation-Location
or nextLink
URL returned by the service.
✅ DO use a required query parameter named api-version
on every operation for the client to specify the API version.
✅ DO use YYYY-MM-DD
date values, with a -preview
suffix for preview versions, as the valid values for api-version
.
✅ DO return HTTP 400 with error code "MissingApiVersionParameter" and message "The api-version query parameter (?api-version=) is required for all requests" if client omits the api-version
query parameter.
✅ DO return HTTP 400 with error code "UnsupportedApiVersionValue" and message "Unsupported api-version '{0}'. The supported api-versions are '{1}'." if client passes an api-version
value unrecognized by the service. For the supported api-versions, just list all the stable versions still supported by the service and just the latest public preview version (if any).
PUT https://service.azure.com/users/Jeff?api-version=2021-06-04
✅ DO use a later date for each new preview version
When releasing a new preview, the service team may completely retire any previous preview versions after giving customers at least 90 days to upgrade their code
⛔ DO NOT introduce any breaking changes into the service.
⛔ DO NOT include a version number segment in any operation path.
⛔ DO NOT use the same date when transitioning from a preview API to a GA API. If the preview api-version
is '2021-06-04-preview', the GA version of the API must be a date later than 2021-06-04
⛔ DO NOT keep a preview feature in preview for more than 1 year; it must go GA (or be removed) within 1 year after introduction.
While removing a value from an enum is a breaking change, adding value to an enum can be handled with an extensible enum. An extensible enum is a string value that has been marked with a special marker - setting modelAsString
to true within an x-ms-enum
block. For example:
"createdByType": {
"type": "string",
"description": "The type of identity that created the resource.",
"enum": [
"User",
"Application",
"ManagedIdentity",
"Key"
],
"x-ms-enum": {
"name": "createdByType",
"modelAsString": true
}
}
☑️ You SHOULD use extensible enums unless you are positive that the symbol set will NEVER change over time.
When the API Versioning guidance above cannot be followed and the Azure Breaking Change Reviewers approve a breaking change to a specific API version it must be communicated to its callers. The API version that is being deprecated must add the azure-deprecating
response header with a semicolon-delimited string notifying the caller what is being deprecated, when it will no longer function, and a URL linking to more information such as what new operation they should use instead.
The purpose is to inform customers (when debugging/logging responses) that they must take action to modify their call to the service's operation and use a newer API version or their call will soon stop working entirely. It is not expected that client code will examine/parse this header's value in any way; it is purely informational to a human being. The string is not part of an API contract (except for the semi-colon delimiters) and may be changed/improved at any time without incurring a breaking change.
✅ DO include the azure-deprecating
header in the operation's response only if the operation will stop working in the future and the client must take action in order for it to keep working.
NOTE: We do not want to scare customers with this header.
✅ DO make the header's value a semicolon-delimited string indicating a set of deprecations where each one indicates what is deprecating, when it is deprecating, and a URL to more information.
Deprecations should use the following pattern:
<description> will retire on <date> (<url>)
Multiple deprecations are allowed, semicolon delimited.
Where the following placeholders should be provided:
description
: a human-readable description of what is being deprecateddate
: the target date that this will be deprecated. This should be expressed following the format in ISO 8601, e.g. "2022-10-31".url
: a fully qualified url that the user can follow to learn more about what is being deprecated, preferably to Azure Updates.
For example:
azure-deprecating: API version 2009-27-07 will retire on 2022-12-01 (https://azure.microsoft.com/updates/video-analyzer-retirement);TLS 1.0 & 1.1 will retire on 2020-10-30 (https://azure.microsoft.com/updates/azure-active-directory-registration-service-is-ending-support-for-tls-10-and-11/)
azure-deprecating: Model version 2021-01-15 used in Sentiment analysis will retire on 2022-12-01 (https://aka.ms/ta-modelversions?sentimentAnalysis)
azure-deprecating: TLS 1.0 & 1.1 support will retire on 2022-10-01 (https://devblogs.microsoft.com/devops/deprecating-weak-cryptographic-standards-tls-1-0-and-1-1-in-azure-devops-services/)
⛔ DO NOT introduce this header without approval from Azure Breaking Change Reviewers and an official deprecation notice on Azure Updates.
Fault tolerant applications require that clients retry requests for which they never got a response, and services must handle these retried requests idempotently. In Azure, all HTTP operations are naturally idempotent except for POST used to create a resource and POST when used to invoke an action.
☑️ YOU SHOULD support repeatable requests as defined in OASIS Repeatable Requests Version 1.0 for POST operations to make them retriable.
- The tracked time window (difference between the
Repeatability-First-Sent
value and the current time) MUST be at least 5 minutes. - Document the POST operation's support for the
Repeatability-First-Sent
,Repeatability-Request-ID
, andRepeatability-Result
headers in the API contract and documentation. - Any operation that does not support repeatability headers should return a 501 (Not Implemented) response for any request that contains valid repeatability request headers.
When the processing for an operation may take a significant amount of time to complete, it should be implemented as a long-running operation (LRO). This allows clients to continue running while the operation is being processed. The client obtains the outcome of the operation at some later time through another API call. See the Long Running Operations section in Considerations for Service Design for an introduction to the design of long-running operations.
✅ DO implement an operation as an LRO if the 99th percentile response time is greater than 1s.
⛔ DO NOT implement PATCH as an LRO. If LRO update is required it must be implemented with POST.
In rare instances where an operation may take a very long time to complete, e.g. longer than 15 minutes, it may be better to expose this as a first class resource of the API rather than as an operation on another resource.
There are two basic patterns for long-running operations in Azure. The first pattern is used for a POST and DELETE
operations that initiate the LRO. These return a 202 Accepted
response with a JSON status monitor in the response body.
The second pattern applies only in the case of a PUT operation to create a resource that also involves additional long-running processing.
For guidance on when to use a specific pattern, please refer to Considerations for Service Design, Long Running Operations.
These are described in the following two sections.
A POST or DELETE long-running operation accepts a request from the client to initiate the operation processing and returns a status monitor that reports the operation's progress.
⛔ DO NOT use a long-running POST to create a resource -- use PUT as described below.
✅ DO allow the client to pass an Operation-Id
header with an ID for the operation's status monitor.
✅ DO generate an ID (typically a GUID) for the status monitor if the Operation-Id
header was not passed by the client.
✅ DO fail a request with a 400-BadRequest
if the Operation-Id
header matches an existing operation unless the request is identical to the prior request (a retry scenario).
✅ DO perform as much validation as practical when initiating the operation to alert clients of errors early.
✅ DO return a 202-Accepted
status code from the request that initiates an LRO if the processing of the operation was successfully initiated (except for "PUT with additional processing" type LRO).
2xx
status code from the initial request of an LRO -- return 202-Accepted
and a status monitor even if processing was completed before the initiating request returns.
✅ DO return a status monitor in the response body as described in Obtaining status and results of long-running operations.
☑️ YOU SHOULD include an Operation-Location
header in the response with the absolute URL of the status monitor for the operation.
☑️ YOU SHOULD include the api-version
query parameter in the Operation-Location
header with the same version passed on the initial request if it is required by the get operation on the status monitor.
For a PUT (create or replace) with additional long-running processing:
✅ DO allow the client to pass an Operation-Id
header with a ID for the status monitor for the operation.
✅ DO generate an ID (typically a GUID) for the status monitor if the Operation-Id
header was not passed by the client.
✅ DO fail a request with a 400-BadRequest
if the Operation-Id
header that matches an existing operation unless the request is identical to the prior request (a retry scenario).
✅ DO perform as much validation as practical when initiating the operation to alert clients of errors early.
✅ DO return a 201-Created
status code for create or 200-OK
for replace from the initial request with a representation of the resource if the resource was created successfully.
✅ DO include an Operation-Id
header in the response with the ID of the status monitor for the operation.
✅ DO include response headers with any additional values needed for a GET request to the status monitor (e.g. location).
☑️ YOU SHOULD include an Operation-Location
header in the response with the absolute URL of the status monitor for the operation.
☑️ YOU SHOULD include the api-version
query parameter in the Operation-Location
header with the same version passed on the initial request if it is required by the get operation on the status monitor.
For all long-running operations, the client will issue a GET on a status monitor resource to obtain the current status of the operation.
✅ DO support the GET method on the status monitor endpoint that returns a 200-OK
response with the current state of the status monitor.
☑️ YOU SHOULD allow any valid value of the api-version
query parameter to be used in the get operation on the status monitor.
Note: Clients may replace the value of api-version
in the Operation-Location
URI with a value appropriate for their application.
✅ DO return a status monitor in the response body that conforms with the following structure:
OperationStatus : Object
Property | Type | Required | Description |
---|---|---|---|
id |
string | true | The unique id of the operation |
status |
string | true | enum that includes values "NotStarted", "Running", "Succeeded", "Failed", and "Canceled" |
error |
ErrorDetail | Error object that describes the error when status is "Failed" | |
result |
object | Only for POST action-type LRO, the results of the operation when completed successfully | |
additional properties |
Additional named or dynamic properties of the operation |
✅ DO include the id
of the operation and any other values needed for the client to form a GET request to the status monitor (e.g. a location
path parameter).
✅ DO include a Retry-After
header in the response to GET requests to the status monitor if the operation is not complete. The value of this header should be an integer number of seconds to wait before making the next request to the status monitor.
✅ DO include the result
property (if any) in the status monitor for a POST action-type long-running operation when the operation completes successfully.
⛔ DO NOT include a result
property in the status monitor for a long-running operation that is not a POST action-type long-running operation.
✅ DO retain the status monitor resource for some publicly documented period of time (at least 24 hours) after the operation completes.
Many services need to store and retrieve data files. For this scenario, the service should not implement its own storage APIs and should instead leverage the existing Azure Storage service. When doing this, the customer "owns" the storage account and just tells your service to use it. Colloquially, we call this Bring Your Own Storage as the customer is bringing their storage account to another service. BYOS provides significant benefits to service implementors: security, performance, uptime, etc. And, of course, most Azure customers are already familiar with the Azure Storage service.
While Azure Managed Storage may be easier to get started with, as your service evolves and matures, BYOS provides the most flexibility and implementation choices. Further, when designing your APIs, be cognizant of expressing storage concepts and how clients will access your data. For example, if you are working with blobs, then you should not expose the concept of folders.
✅ DO use the Bring Your Own Storage pattern.
✅ DO use a blob prefix for a logical folder (avoid terms such as directory
, folder
, or path
).
⛔ DO NOT require a fresh container per operation.
✅ DO use managed identity and Role Based Access Control (RBAC) as the mechanism allowing customers to grant permission to their Storage account to your service.
✅ DO Add RBAC roles for every service operation that requires accessing Storage scoped to the exact permissions.
✅ DO Ensure that RBAC roles are backward compatible, and specifically, do not take away permissions from a role that would break the operation of the service. Any change of RBAC roles that results in a change of the service behavior is considered a breaking change.
It is not uncommon to rely on other services, e.g. storage, when implementing your service. Inevitably, the services you depend on will fail. In these situations, you can include the downstream error code and text in the inner-error of the response body. This provides a consistent pattern for handling errors in the services you depend upon.
✅ DO include error from downstream services as the 'inner-error' section of the response body.
Generally speaking, there are two patterns that you will encounter when working with files; single file access, and file collections.
Designing an API for accessing a single file, depending on your scenario, is relatively straight forward.
✔️ YOU MAY use a Shared Access Signature SAS to provide access to a single file. SAS is considered the minimum security for files and can be used in lieu of, or in addition to, RBAC.
☑️ YOU SHOULD if using HTTP (not HTTPS) document to users that all information is sent over the wire in clear text.
✅ DO return an HTTP status code representing the result of your service operation's behavior.
✅ DO include the Storage error information in the 'inner-error' section of an error response if the error was the result of an internal Storage operation failure. This helps the client determine the underlying cause of the error, e.g.: a missing storage object or insufficient permissions.
✅ DO allow the customer to specify a URL path to a single Storage object if your service requires access to a single file.
✔️ YOU MAY allow the customer to provide a last-modified timestamp (in RFC1123 format) for read-only files. This allows the client to specify exactly which version of the files your service should use. When reading a file, your service passes this timestamp to Azure Storage using the if-unmodified-since request header. If the Storage operation fails with 412, the Storage object was modified and your service operation should return an appropriate 4xx status code and return the Storage error in your operation's 'inner-error' (see guideline above).
✅ DO allow the customer to specify a URL path to a logical folder (via prefix and delimiter) if your service requires access to multiple files (within this folder). For more information, see List Blobs API
✔️ YOU MAY offer an extensions
field representing an array of strings indicating file extensions of desired blobs within the logical folder.
A common pattern when working with multiple files is for your service to receive requests that contain the location(s) of files to process ("input") and a location(s) to place any files that result from processing ("output"). Note: the terms "input" and "output" are just examples; use terms more appropriate to your service's domain.
For example, a service's request body to configure BYOS may look like this:
{
"input":{
"location": "https://mycompany.blob.core.windows.net/documents/english/?<sas token>",
"delimiter": "/",
"extensions" : [ ".bmp", ".jpg", ".tif", ".png" ],
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT"
},
"output":{
"location": "https://mycompany.blob.core.windows.net/documents/spanish/?<sas token>",
"delimiter":"/"
}
}
Depending on the requirements of the service, there can be any number of "input" and "output" sections, including none.
✅ DO include a JSON object that has string values for "location" and "delimiter". For "location", the customer must pass a URL to a blob prefix which represents a directory. For "delimiter", the customer must specify the delimiter character they desire to use in the location URL; typically "/" or "".
✔️ YOU MAY support the "lastModified" field for input directories (see guideline above).
✅ DO support a "location" URL with a container-scoped SAS that has a minimum of listing
and read
permissions for input directories.
✅ DO support a "location" URL with a container-scoped SAS that has a minimum of write
permissions for output directories.
The HTTP Standard defines request headers that clients may use to specify a precondition
for execution of an operation. These headers allow clients to implement efficient caching mechanisms
and avoid data loss in the event of concurrent updates to a resource. The headers that specify conditional execution are If-Match
, If-None-Match
, If-Modified-Since
, If-Unmodified-Since
, and If-Range
.
✅ DO honor any precondition headers received as part of a client request.
The HTTP Standard does not allow precondition headers to be ignored, as it can be unsafe to do so.
✅ DO return the appropriate precondition failed error response if the service cannot verify the truth of the precondition.
Note: The Azure Breaking Changes review board will allow a GA service that currently ignores precondition headers to begin honoring them in a new API version without a formal breaking change notification. The potential for disruption to customer applications is low and outweighed by the value of conforming to HTTP standards.
While conditional requests can be implemented using last modified dates, entity tags ("ETags") are strongly preferred since last modified dates cannot distinguish updates made less than a second apart.
☑️ YOU SHOULD return an ETag
with any operation returning the resource or part of a resource or any update of the resource (whether the resource is returned or not).
This section gives a summary of the processing to perform for precondition headers. See the Conditional Requests section of the HTTP Standard for details on how and when to evaluate these headers.
✅ DO adhere to the following table for processing a GET request with precondition headers:
GET Request | Return code | Response |
---|---|---|
ETag value = If-None-Match value |
304-Not Modified |
no additional information |
ETag value != If-None-Match value |
200-OK |
Response body include the serialized value of the resource (typically JSON) |
For more control over caching, please refer to the cache-control
HTTP header.
✅ DO adhere to the following table for processing a PUT, PATCH, or DELETE request with precondition headers:
Operation | Header | Value | ETag check | Return code | Response |
---|---|---|---|---|---|
PATCH / PUT | If-None-Match |
* | check for any version of the resource ('*' is a wildcard used to match anything), if none are found, create the resource. | 200-OK or 201-Created |
Response header MUST include the new ETag value. Response body SHOULD include the serialized value of the resource (typically JSON). |
PATCH / PUT | If-None-Match |
* | check for any version of the resource, if one is found, fail the operation | 412-Precondition Failed |
Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. |
PATCH / PUT | If-Match |
value of ETag | value of If-Match equals the latest ETag value on the server, confirming that the version of the resource is the most current |
200-OK or 201-Created |
Response header MUST include the new ETag value. Response body SHOULD include the serialized value of the resource (typically JSON). |
PATCH / PUT | If-Match |
value of ETag | value of If-Match header DOES NOT equal the latest ETag value on the server, indicating a change has ocurred since after the client fetched the resource |
412-Precondition Failed |
Response body SHOULD return the serialized value of the resource (typically JSON) that was passed along with the request. |
DELETE | If-Match |
value of ETag | value matches the latest value on the server | 204-No Content |
Response body SHOULD be empty. |
DELETE | If-Match |
value of ETag | value does NOT match the latest value on the server | 412-Preconditioned Failed |
Response body SHOULD be empty. |
The strategy that you use to compute the ETag
depends on its semantic. For example, it is natural, for resources that are inherently versioned, to use the version as the value of the ETag
. Another common strategy for determining the value of an ETag
is to use a hash of the resource. If a resource is not versioned, and unless computing a hash is prohibitively expensive, this is the preferred mechanism.
☑️ YOU SHOULD use a hash of the representation of a resource rather than a last modified/version number
While it may be tempting to use a revision/version number for the resource as the ETag, it interferes with client's ability to retry update requests. If a client sends a conditional update request, the service acts on the request, but the client never receives a response, a subsequent identical update will be seen as a conflict even though the retried request is attempting to make the same update.
☑️ YOU SHOULD, if using a hash strategy, hash the entire resource.
☑️ YOU SHOULD, if supporting range requests, use a strong ETag in order to support caching.
✔️ YOU MAY use or, include, a timestamp in your resource schema. If you do this, the timestamp shouldn't be returned with more than subsecond precision, and it SHOULD be consistent with the data and format returned, e.g. consistent on milliseconds.
✔️ YOU MAY consider Weak ETags if you have a valid scenario for distinguishing between meaningful and cosmetic changes or if it is too expensive to compute a hash.
✅ DO, when supporting multiple representations (e.g. Content-Encodings) for the same resource, generate different ETag values for the different representations.
All string values in JSON are inherently Unicode and UTF-8 encoded, but clients written in a high-level programming language must work with strings in that language's string encoding, which may be UTF-8, UTF-16, or CodePoints (UTF-32). When a service response includes a string offset or length value, it should specify these values in all 3 encodings to simplify client development and ensure customer success when isolating a substring.
✅ DO include all 3 encodings (UTF-8, UTF-16, and CodePoint) for every string offset or length value in a service response.
✅ DO define every string offset or length value in a service response as an object with the following structure:
Property | Type | Required | Description |
---|---|---|---|
utf8 |
integer | true | The offset or length of the substring in UTF-8 encoding |
utf16 |
integer | true | The offset or length of the substring in UTF-16 encoding |
codePoint |
integer | true | The offset or length of the substring in CodePoint encoding |
Azure SDK client guidelines specify that client libraries must send telemetry data through the User-Agent
header, X-MS-UserAgent
header, and Open Telemetry.
Client libraries are required to send telemetry and distributed tracing information on every request. Telemetry information is vital to the effective operation of your service and should be a consideration from the outset of design and implementation efforts.
✅ DO follow the Azure SDK client guidelines for supporting telemetry headers and Open Telemetry.
⛔ DO NOT reject a call if you have custom headers you don't understand, and specifically, distributed tracing headers.
Additional References
- Azure SDK client guidelines
- Azure SDK User-Agent header policy
- Azure SDK Distributed tracing policy
- Open Telemetry
In addition to distributed tracing, Azure also uses a set of common correlation headers:
Name | Applies to | Description |
---|---|---|
x-ms-client-request-id | Both | Optional. Caller-specified value identifying the request, in the form of a GUID with no decoration such as curly braces (e.g. x-ms-client-request-id: 9C4D50EE-2D56-4CD3-8152-34347DC9F2B0 ). If the caller provides this header the service must include this in their log entries to facilitate correlation of log entries for a single request. Because this header can be client-generated, it should not be assumed to be unique by the service implementation. |
x-ms-request-id | Response | Required. Service generated correlation id identifying the request, in the form of a GUID with no decoration such as curly braces. In contrast to the the x-ms-client-request-id , the service must ensure that this value is globally unique. Services should log this value with their traces to facilitate correlation of log entries for a single request. |
These guidelines describe the upfront design considerations, technology building blocks, and common patterns that Azure teams encounter when building an API for their service. There is a great deal of information in them that can be difficult to follow. Fortunately, at Microsoft, there is a team committed to ensuring your success.
The Azure REST API Stewardship board is a collection of dedicated architects that are passionate about helping Azure service teams build interfaces that are intuitive, maintainable, consistent, and most importantly, delight our customers. Because APIs affect nearly all downstream decisions, you are encouraged to reach out to the Stewardship board early in the development process. These architects will work with you to apply these guidelines and identify any hidden pitfalls in your design. For more information on how to part with the Stewardship board, please refer to Considerations for Service Design.