├── .github └── CODEOWNERS ├── CONTRIBUTING.md ├── Guidelines.md ├── README.md ├── SECURITY.md ├── azure ├── .markdownlint.json ├── ConsiderationsForServiceDesign.md ├── Guidelines.md ├── README.md └── VersioningGuidelines.md ├── graph ├── Guidelines-deprecated.md ├── GuidelinesGraph.md ├── ModelExample.png ├── articles │ ├── collections.md │ ├── coreTypes.md │ ├── deprecation.md │ ├── errorResponses.md │ ├── filter-as-segment.md │ ├── naming.md │ └── nullable.md └── patterns │ ├── LRO.gif │ ├── PatternDescriptionTemplate.md │ ├── RELO.gif │ ├── alternate-key.md │ ├── antiPatternTemplate.md │ ├── change-tracking.md │ ├── default-properties.md │ ├── dictionary-client-guidance.md │ ├── dictionary.md │ ├── enums.md │ ├── evolvable-enums.md │ ├── facets.md │ ├── flat-bag.md │ ├── long-running-operations.md │ ├── namespace.md │ ├── navigation-property.md │ ├── operations.md │ ├── subsets.md │ ├── subtypes.md │ ├── upsert.md │ └── viewpoint.md └── license.txt /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These are the set of folks who should review PRs on the azureRestUpdates branch. 2 | #* @microsoft/azure-api-stewardship-board @Azure/api-stewardship-board 3 | /graph/ @microsoft/graphguidelinesapprovers 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Microsoft REST API Guidelines 2 | The Microsoft REST API Guidelines is a Microsoft-wide initiative to develop consistent design guidelines for REST APIs. The initiative requires input and feedback from a variety of individuals both inside and outside of Microsoft. 3 | 4 | To provide feedback, please follow the guidance in this document. Please note that these are just guidelines, not rules. Use your best judgment and feel free to propose changes to anything in this repository, including the contribution guidelines. 5 | 6 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 7 | - [Creating issues](#creating-issues) 8 | - [Recommended setup for contributing](#recommended-setup-for-contributing) 9 | - [Documentation styleguide](#documentation-styleguide) 10 | - [Commit messages](#commit-messages) 11 | - [Pull requests](#pull-requests) 12 | 13 | ## Creating issues 14 | - You can [create an issue][new-issue], but before doing that please read the bullets below and include as many details as possible. 15 | - Perform a [cursory search][issue-search] to see if a similar issue has already been submitted. 16 | - Reference the version of the Microsoft REST API Guidelines you are using. 17 | - Include the guidance you expected and other places you've seen that guidance, e.g. [White House Web API Standards][white-house-api-guidelines]. 18 | - Include sample requests and responses whenever possible. 19 | 20 | ### Related repositories 21 | This is the repository for Microsoft REST API Guidelines documentation only. Please ensure that you are opening issues in the right repository. 22 | 23 | ## Recommended setup for contributing 24 | - Fork this repository on GitHub 25 | - Install [Git][git] to your computer and clone your new forked repository 26 | - Install [Atom][atom], [VS Code][vscode], or your favorite editor 27 | - Install [markdown-toc package][markdown-toc] 28 | 29 | ## Documentation styleguide 30 | - Use [GitHub-flavored markdown][gfm] 31 | - Use syntax-highlighted examples liberally 32 | - Trim trailing empty lines from HTTP requests 33 | - Retain only essential headers for understanding the example 34 | - Use valid (e.g., member names quoted), pretty-printed JSON with a 2 space indent 35 | - Minimize JSON payloads by using ellipses 36 | - Write one sentence per line. 37 | 38 | ### Example 39 | #### Request 40 | 41 | ```http 42 | GET http://services.odata.org/V4/TripPinServiceRW/People HTTP/1.1 43 | Accept: application/json 44 | ``` 45 | 46 | #### Response 47 | 48 | ```http 49 | HTTP/1.1 200 OK 50 | Content-Type: application/json 51 | 52 | { 53 | "@nextLink":"http://services.odata.org/V4/TripPinServiceRW/People?$skiptoken=8", 54 | "value":[ 55 | { 56 | "userName":"russellwhyte", 57 | "firstName":"Russell", 58 | "lastName":"Whyte", 59 | "emails":[ 60 | "Russell@example.com", 61 | "Russell@contoso.com" 62 | ], 63 | "addressInfo":[ 64 | { 65 | "address":"187 Suffolk Ln.", 66 | "city":{ 67 | "countryRegion":"United States", 68 | "name":"Boise", 69 | "region":"ID" 70 | } 71 | } 72 | ], 73 | "gender":"Male", 74 | }, 75 | ... 76 | ] 77 | } 78 | ``` 79 | 80 | ## Commit messages 81 | - Use the present tense: "Change ...", not "Changed ..." 82 | - Use the imperative mood: "Change ...", not "Changes ..." 83 | - Limit the first line to 72 characters or less 84 | - Reference issues and pull requests liberally 85 | 86 | ## Pull requests 87 | Pull requests serve as the primary mechanism by which contributions are proposed and accepted. We recommend creating a [topic branch][topic-branch] and sending a pull request to the `vNext` branch from the topic branch. For additional guidance, read through the [GitHub Flow Guide][github-flow-guide]. 88 | 89 | Be prepared to address feedback on your pull request and iterate if necessary. 90 | 91 | [code-of-conduct]: https://opensource.microsoft.com/codeofconduct/ 92 | [new-issue]: https://github.com/Microsoft/api-guidelines/issues/new 93 | [issue-search]: https://github.com/Microsoft/api-guidelines/issues 94 | [white-house-api-guidelines]: https://github.com/WhiteHouse/api-standards/blob/master/README.md 95 | [topic-branch]: https://www.git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#Topic-Branches 96 | [gfm]: https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown 97 | [github-flow-guide]: https://guides.github.com/introduction/flow/ 98 | [atom-beautify]: https://atom.io/packages/atom-beautify 99 | [atom]: https://atom.io/ 100 | [markdown-toc]: https://atom.io/packages/markdown-toc 101 | [vscode]: https://code.visualstudio.com/ 102 | [git]: https://git-scm.com/ 103 | -------------------------------------------------------------------------------- /Guidelines.md: -------------------------------------------------------------------------------- 1 | > # NOTICE TO READERS 2 | > This document has been deprecated and has been moved to the [Microsoft REST API Guidelines deprecated](./graph/Guidelines-deprecated.md). Please refer to the notes below for the latest guidance. 3 | > 4 | > ## **Guidance for Azure service teams** 5 | > Azure service teams should use the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, see the [README](./azure/README.md) in the Azure folder. 6 | > 7 | > ## **Guidance for Microsoft Graph service teams** 8 | > Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provides a refined set of guidance targeted specifically for Microsoft Graph services. 9 | 10 | --- 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft REST API Guidelines 2 | Thank you for your interest in the Microsoft REST API Guidelines. If you have landed here, you're probably interested in learning about APIs. If so, you are in the right place! 3 | We publish these guidelines here with the aim of fostering dialogue and learning in the API community at large. We further hope that these guidelines might encourage other organizations to create guidelines that are appropriate for them and in turn, if they're able, to publish theirs. 4 | 5 | ### Guidance for Azure service teams 6 | Azure service teams should reference the companion documents, [Azure REST API Guidelines](./azure/Guidelines.md) and [Considerations for Service Design](./azure/ConsiderationsForServiceDesign.md), when building or modifying their services. These documents provide a refined set of guidance targeted specifically for Azure services. For more information, please refer to the [README](./azure/README.md) in the Azure folder. 7 | 8 | ### Guidance for Microsoft Graph service teams 9 | Graph service teams should reference the companion document, [Microsoft Graph REST API Guidelines](./graph/GuidelinesGraph.md) when building or modifying their services. This document and the associated pattern catalog provide a refined set of guidance targeted specifically for Microsoft Graph services. 10 | 11 | [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) 12 | 13 | ## Code of Conduct 14 | This project adopts the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | 16 | ## This repository 17 | This repository contains a collection of documents and related materials supporting the overall Microsoft REST API Guidelines initiative. To contribute to this repository, see the [contribution guidelines][contribution-guidance]. 18 | 19 | [contribution-guidance]: CONTRIBUTING.md 20 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /azure/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD012": { 4 | "maximum": 2 5 | }, 6 | "MD013": { 7 | "line_length": 500 8 | }, 9 | "MD022": false, 10 | "MD031": false, 11 | "MD032": false, 12 | "MD033": { 13 | "allowed_elements": [ "sup" ] 14 | }, 15 | "MD036": false 16 | } -------------------------------------------------------------------------------- /azure/README.md: -------------------------------------------------------------------------------- 1 | # Azure REST API Guidance 2 | When building software components that will be used by developers to build other software, providing APIs that are easy to use, fit to purpose, scalable, maintainable, and consistent across your product can make the difference between success and failure for your software. 3 | 4 | Designing powerful APIs with strong defaults, consistent behavior across related projects, and ease of use for developers arises from putting yourself in the shoes of the person using your interfaces, and taking their concerns to heart. The APIs you ship can have a dramatic long term effect on the health of your software product, and that is why the **REST API Stewardship Board** is here to help! We have published a collection of best practices, REST guidance, and OpenAPI style guidelines to help you create an amazing developer experience. 5 | * [Considerations for Service Design](ConsiderationsForServiceDesign.md) 6 | * [REST API Guidelines](Guidelines.md) 7 | * [OpenAPI Style Guidelines](https://github.com/Azure/azure-api-style-guide/blob/main/README.md) 8 | * [Versioning policy for Azure services, SDKs, and CLI tools](https://learn.microsoft.com/en-us/azure/developer/intro/azure-service-sdk-tool-versioning) 9 | * [Breaking Changes](https://aka.ms/azapi/breakingchanges) Note: Internal Microsoft link 10 | 11 | You can reach out to us via [email](mailto://azureapirbcore@microsoft.com) or in our [Teams](https://teams.microsoft.com/l/team/19%3a3ebb18fded0e47938f998e196a52952f%40thread.tacv2/conversations?groupId=1a10b50c-e870-4fe0-8483-bf5542a8d2d8&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) channel. 12 | 13 | Note: The Teams channel is internal MS. 14 | -------------------------------------------------------------------------------- /azure/VersioningGuidelines.md: -------------------------------------------------------------------------------- 1 | # Azure Versioning Guidelines 2 | 3 | ## History 4 | 5 |
6 | Expand change history 7 | 8 | | Date | Notes | 9 | | ----------- | -------------------------------------------------------------- | 10 | | 2024-Nov-14 | Azure Service Versioning & Breaking Change Guidelines | 11 | 12 |
13 | 14 | ## Guidelines 15 | 16 | This document provides a "Dos and Don'ts" list for complying with the Azure Versioning and Breaking Change Policy, 17 | as documented [internally](aka.ms/AzBreakingChangesPolicy) and [externally](https://learn.microsoft.com/azure/developer/intro/azure-service-sdk-tool-versioning). 18 | 19 | :white_check_mark: **DO** thoroughly ensure/test the API contract is entirely correct before merging it into a production branch of the specs repo. 20 | 21 | Testing helps avoid "BugFix" changes to the API definition. Testing should be done at the HTTP level as well as through generated SDKs. 22 | 23 | :white_check_mark: **DO** retire all prior preview API versions 90 days after a new GA or preview API version is released. 24 | 25 | :white_check_mark: **DO** contact the Azure Breaking Change Review board to coordinate communications to customers 26 | when releasing an API version requiring the retirement of a prior version. 27 | 28 | :white_check_mark: **DO** create a new preview API version for any features that should remain in preview following a new GA release. 29 | 30 | :white_check_mark: **DO** use a date strictly later than the most recent GA API version when releasing 31 | a new preview API version. 32 | 33 | :white_check_mark: **DO** deprovision any API version that has been retired. Retired APIs versions should behave like 34 | an unknown API version (see [ref](https://aka.ms/azapi/guidelines#versioning-api-version-unsupported)). 35 | 36 | :white_check_mark: **DO** remove retired API versions from the azure-rest-api-specs repo. 37 | 38 | :white_check_mark: **DO** review any change to service behavior that could disrupt customers with the Azure Breaking Changes review board, even if the change is not part of the API definition. 39 | 40 | Some examples of behavior changes that must be reviewed are: 41 | - Introducing or changing rate limits to be more restrictive than previously 42 | - Changing the permissions required to successfully execute an operation 43 | 44 | :no_entry: **DO NOT** change the behavior of an API version that is available to customers either in public preview or GA. 45 | Changes in behavior should always be introduced in a new API version, with prior versions working as before. 46 | 47 | :no_entry: **DO NOT** introduce breaking changes from a prior GA version just to satisfy ARM or Azure API guidelines. 48 | 49 | Avoiding breaking changes in a GA API takes precedence over adherence to API guidelines and resolving linter errors. 50 | 51 | :no_entry: **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. 52 | -------------------------------------------------------------------------------- /graph/ModelExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/api-guidelines/3db3462a9361132adf60da74cff3aced08eb9ff7/graph/ModelExample.png -------------------------------------------------------------------------------- /graph/articles/collections.md: -------------------------------------------------------------------------------- 1 | # Collections 2 | 3 | ## 1. Item keys 4 | 5 | Services SHOULD support durable identifiers for each item in the collection, and that identifier SHOULD be represented in JSON as "id". These durable identifiers are often used as item keys. 6 | 7 | Collections MAY support delta queries, see the [Change Tracking pattern](../patterns/change-tracking.md) section for more details. 8 | 9 | ## 2. Serialization 10 | 11 | Collections are represented in JSON using standard array notation for `value` property. 12 | 13 | ## 3. Collection URL patterns 14 | 15 | While there are multiple collections located directly under the Graph root going forward, you MUST have a singleton for the top-level segment and scope collections to an appropriate singleton. Collection names SHOULD be plural nouns when possible. Collection names shouldn't use suffixes, such as "Collection" or "List". 16 | 17 | For example: 18 | 19 | ```http 20 | GET https://graph.microsoft.com/v1.0/teamwork/devices 21 | ``` 22 | 23 | Collections elements MUST be addressable by a unique id property. The id property MUST be a String and MUST be unique within the collection. The id property MUST be represented in JSON as "id". 24 | For example: 25 | 26 | ```http 27 | GET https://graph.microsoft.com/beta/teamwork/devices/0f3ce432-e432-0f3c-32e4-3c0f32e43c0f 28 | ``` 29 | 30 | Where: 31 | 32 | - "https://graph.microsoft.com/beta/teamwork" - the service root represented as the combination of host (site URL) + the root path to the service. 33 | - "devices" – the name of the collection, unabbreviated, pluralized. 34 | - "0f3ce432-e432-0f3c-32e4-3c0f32e43c0f" – the value of the unique id property that MUST be the raw string/number/guid value with no quoting but properly escaped to fit in a URL segment. 35 | 36 | ### 3.1. Nested collections and properties 37 | 38 | Collection items MAY contain other collections. 39 | For example, a devices collection MAY contain device resources that have multiple mac addresses: 40 | 41 | ```http 42 | GET https://graph.microsoft.com/beta/teamwork/devices/0f3ce432-e432-0f3c-32e4-3c0f32e43c0f 43 | ``` 44 | 45 | ```json 46 | 47 | { 48 | "value": { 49 | "@odata.type": "#microsoft.graph.teamworkDevice", 50 | "id": "0f3ce432-e432-0f3c-32e4-3c0f32e43c0f", 51 | "deviceType": "CollaborationBar", 52 | "hardwareDetail": { 53 | "serialNumber": "0189", 54 | "uniqueId": "5abcdefgh", 55 | "macAddresses": [], 56 | "manufacturer": "yealink", 57 | "model": "vc210" 58 | }, 59 | ... 60 | } 61 | } 62 | ``` 63 | 64 | ## 4. Big collections 65 | 66 | As data grows, so do collections. 67 | Services SHOULD support server-side pagination from day one even for all collections, as adding pagination is a breaking change. 68 | When multiple pages are available, the serialization payload MUST contain the opaque URL for the next page as appropriate. 69 | Refer to the [paging guidance](../Guidelines-deprecated.md#98-pagination) for more details. 70 | 71 | Clients MUST be resilient to collection data being either paged or nonpaged for any given request. 72 | 73 | ```json 74 | { 75 | "value":[ 76 | { "id": "Item 1","price": 9 95,"sizes": null}, 77 | { … }, 78 | { … }, 79 | { "id": "Item 99","price": 5 99,"sizes": null} 80 | ], 81 | "@nextLink": "{opaqueUrl}" 82 | } 83 | ``` 84 | 85 | ## 5. Changing collections 86 | 87 | POST requests are not idempotent. 88 | This means that two POST requests sent to a collection resource with exactly the same payload MAY lead to multiple items being created in that collection. 89 | This is often the case for insert operations on items with a server-side generated id. 90 | For additional information refer to [Upsert pattern](../patterns/upsert.md). 91 | 92 | For example, the following request: 93 | 94 | ```http 95 | POST https://graph.microsoft.com/beta/teamwork/devices 96 | ``` 97 | 98 | Would lead to a response indicating the location of the new collection item: 99 | 100 | ```http 101 | 201 Created 102 | Location: https://graph.microsoft.com/beta/teamwork/devices/123 103 | ``` 104 | 105 | And once executed again, would likely lead to another resource: 106 | 107 | ```http 108 | 201 Created 109 | Location: https://graph.microsoft.com/beta/teamwork/devices/124 110 | ``` 111 | 112 | ## 6. Sorting collections 113 | 114 | The results of a collection query MAY be sorted based on property values. 115 | The property is determined by the value of the _$orderBy_ query parameter. 116 | 117 | The value of the _$orderBy_ parameter contains a comma-separated list of expressions used to sort the items. 118 | A special case of such an expression is a property path terminating on a primitive property. 119 | 120 | The expression MAY include the suffix "asc" for ascending or "desc" for descending, separated from the property name by one or more spaces. 121 | If "asc" or "desc" is not specified, the service MUST order by the specified property in ascending order. 122 | 123 | NULL values MUST sort as "less than" non-NULL values. 124 | 125 | Items MUST be sorted by the result values of the first expression, and then items with the same value for the first expression are sorted by the result value of the second expression, and so on. 126 | The sort order is the inherent order for the type of the property. 127 | 128 | For example: 129 | 130 | ```http 131 | GET https://graph.microsoft.com/beta/teamwork/devices?$orderBy=companyAssetTag 132 | ``` 133 | 134 | Will return all devices sorted by companyAssetTag in ascending order. 135 | 136 | For example: 137 | 138 | ```http 139 | GET https://graph.microsoft.com/beta/teamwork/devices?$orderBy=companyAssetTag desc 140 | ``` 141 | 142 | Will return all devices sorted by companyAssetTag in descending order. 143 | 144 | Sub-sorts can be specified by a comma-separated list of property names with OPTIONAL direction qualifier. 145 | 146 | For example: 147 | 148 | ```http 149 | GET https://graph.microsoft.com/beta/teamwork/devices?$orderBy=companyAssetTag desc,activityState 150 | ``` 151 | 152 | Will return all devices sorted by companyAssetTag in descending order and a secondary sort order of activityState in ascending order. 153 | 154 | Sorting MUST compose with filtering see [Odata 4.01 spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361038) for more details. 155 | 156 | ### 6.1. Interpreting a sorting expression 157 | 158 | Sorting parameters MUST be consistent across pages, as both client and server-side paging is fully compatible with sorting. 159 | 160 | If a service does not support sorting by a property named in a _$orderBy_ expression, the service MUST respond with an error message as defined in the Responding to Unsupported Requests section. 161 | 162 | ## 7. Filtering 163 | 164 | The _$filter_ querystring parameter allows clients to filter a collection of resources that are addressed by a request URL. 165 | The expression specified with _$filter_ is evaluated for each resource in the collection, and only items where the expression evaluates to true are included in the response. 166 | Resources for which the expression evaluates to false or to null, or which reference properties that are unavailable due to permissions, are omitted from the response. 167 | 168 | Example: return all devices with activity state equal to 'Active' 169 | 170 | ```http 171 | GET https://graph.microsoft.com/beta/teamwork/devices?$filter=(activityState eq 'Active') 172 | ``` 173 | 174 | The value of the _$filter_ option is a Boolean expression. 175 | 176 | ### 7.1. Filter operations 177 | 178 | Services that support _$filter_ SHOULD support the following minimal set of operations. 179 | 180 | Operator | Description | Example 181 | -------------------- | --------------------- | ----------------------------------------------------- 182 | Comparison Operators | | 183 | eq | Equal | city eq 'Redmond' 184 | ne | Not equal | city ne 'London' 185 | gt | Greater than | price gt 20 186 | ge | Greater than or equal | price ge 10 187 | lt | Less than | price lt 20 188 | le | Less than or equal | price le 100 189 | Logical Operators | | 190 | and | Logical and | price le 200 and price gt 3.5 191 | or | Logical or | price le 3.5 or price gt 200 192 | not | Logical negation | not price le 3.5 193 | Grouping Operators | | 194 | ( ) | Precedence grouping | (priority eq 1 or city eq 'Redmond') and price gt 100 195 | 196 | Services MUST use the following operator precedence for supported operators when evaluating _$filter_ expressions. 197 | Operators are listed by category in order of precedence from highest to lowest. 198 | Operators in the same category have equal precedence: 199 | 200 | | Group | Operator | Description | 201 | |:----------------|:---------|:----------------------| 202 | | Grouping | ( ) | Precedence grouping | 203 | | Unary | not | Logical Negation | 204 | | Relational | gt | Greater Than | 205 | | | ge | Greater Than or Equal | 206 | | | lt | Less Than | 207 | | | le | Less Than or Equal | 208 | | Equality | eq | Equal | 209 | | | ne | Not Equal | 210 | | Conditional AND | and | Logical And | 211 | | Conditional OR | or | Logical Or | 212 | 213 | ## 8. Pagination 214 | 215 | RESTful APIs that return collections MAY return partial sets. 216 | Consumers of these services MUST expect partial result sets and correctly page through to retrieve an entire set. 217 | 218 | There are two forms of pagination that MAY be supported by RESTful APIs. 219 | Server-driven paging allows servers to even out load across clients and mitigates against denial-of-service attacks by forcibly paginating a request over multiple response payloads. 220 | Client-driven paging enables clients to request only the number of resources that it can use at a given time. 221 | 222 | Sorting and Filtering parameters MUST be consistent across pages, because both client- and server-side paging is fully compatible with both filtering and sorting. 223 | 224 | ### 8.1. Server-driven paging 225 | 226 | Paginated responses MUST indicate a partial result by including a `@odata.nextLink` token in the response. 227 | The absence of a `nextLink` token means that no additional pages are available, see [Odata 4.01 spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ServerDrivenPaging) for more details. 228 | 229 | Clients MUST treat the `nextLink` URL as opaque, which means that query options may not be changed while iterating over a set of partial results. 230 | 231 | Example: 232 | 233 | ```http 234 | GET https://graph.microsoft.com/beta/teamwork/devices 235 | Accept: application/json 236 | 237 | HTTP/1.1 200 OK 238 | Content-Type: application/json 239 | 240 | { 241 | "value": [...], 242 | "@odata.nextLink": "{opaqueUrl}" 243 | } 244 | ``` 245 | 246 | ### 8.2. Client-driven paging 247 | 248 | Clients MAY use _$top_ and _$skip_ query parameters to specify a number of results to return and an offset into the collection. 249 | 250 | The server SHOULD honor the values specified by the client; however, clients MUST be prepared to handle responses that contain a different page size or contain a `@odata.nextLink` token. 251 | 252 | When both _$top_ and _$skip_ are given by a client, the server SHOULD first apply _$skip_ and then _$top_ on the collection. 253 | 254 | Note: If the server can't honor _$top_ and/or _$skip_, the server MUST return an error to the client informing about it instead of just ignoring the query options. 255 | This will avoid the risk of the client making assumptions about the data returned. 256 | 257 | Example: 258 | 259 | ```http 260 | GET https://graph.microsoft.com/beta/teamwork/devices?$top=5&$skip=2 261 | 262 | Accept: application/json 263 | 264 | HTTP/1.1 200 OK 265 | Content-Type: application/json 266 | 267 | { 268 | "value": [...] 269 | } 270 | ``` 271 | 272 | ### 8.3. Additional considerations 273 | 274 | **Stable order prerequisite:** Both forms of paging depend on the collection of items having a stable order. 275 | The server MUST supplement any specified order criteria with additional sorts (typically by key) to ensure that items are always ordered consistently. 276 | 277 | **Missing/repeated results:** Even if the server enforces a consistent sort order, results MAY be missing or repeated based on creation or deletion of other resources. 278 | Clients MUST be prepared to deal with these discrepancies. 279 | The server SHOULD always encode the record ID of the last read record, helping the client in the process of managing repeated/missing results. 280 | 281 | **Combining client- and server-driven paging:** Note that client-driven paging does not preclude server-driven paging. 282 | If the page size requested by the client is larger than the default page size supported by the server, the expected response would be the number of results specified by the client, paginated as specified by the server paging settings. 283 | 284 | **Page Size:** Clients MAY request server-driven paging with a specific page size by specifying a _$maxpagesize_ preference. 285 | The server SHOULD honor this preference if the specified page size is smaller than the server's default page size. 286 | 287 | **Paginating embedded collections:** It is possible for both client-driven paging and server-driven paging to be applied to embedded collections. 288 | If a server paginates an embedded collection, it MUST include additional `nextLink` tokens as appropriate. 289 | 290 | **Recordset count:** Developers who want to know the full number of records across all pages, MAY include the query parameter _$count=true_ to tell the server to include the count of items in the response. 291 | 292 | ## 9. Compound collection operations 293 | 294 | Filtering, Sorting and Pagination operations MAY all be performed against a given collection. 295 | When these operations are performed together, the evaluation order MUST be: 296 | 297 | 1. **Filtering**. This includes all range expressions performed as an AND operation. 298 | 2. **Sorting**. The potentially filtered list is sorted according to the sort criteria. 299 | 3. **Pagination**. The materialized paginated view is presented over the filtered, sorted list. This applies to both server-driven pagination and client-driven pagination. 300 | 301 | ## 10. Empty Results 302 | 303 | When a filter is performed on a collection and the result set is empty you MUST respond with a valid response body and a 200 response code. 304 | In this example the filters supplied by the client resulted in a empty result set. 305 | The response body is returned as normal and the _value_ attribute is set to a empty collection. 306 | You SHOULD maintain consistency in your API whenever possible. 307 | 308 | ```http 309 | GET https://graph.microsoft.com/beta/teamwork/devices?$filter=('deviceType' eq 'Collab' or companyAssetTa eq 'Tag1') 310 | Accept: application/json 311 | 312 | HTTP/1.1 200 OK 313 | Content-Type: application/json 314 | 315 | { 316 | "value": [] 317 | } 318 | ``` 319 | -------------------------------------------------------------------------------- /graph/articles/coreTypes.md: -------------------------------------------------------------------------------- 1 | # Core Types 2 | 3 | ## Overview 4 | 5 | Types exist in Microsoft Graph which are highly-connected/central to the Microsoft Graph ecosystem. Often, these types are in the position of being able to contain structural properties relevant to other APIs, because they are connected to many entities in Microsoft Graph. 6 | 7 | Structural properties should be only added to these core types when they are intrinsic to the entity itself, and strictly not for the purpose of convenience due to the entity's position in Microsoft Graph. 8 | 9 | ## Core Types in Microsoft Graph 10 | 11 | The following types are identified as core types, and will require strong justification to allow new structural properties to be added in all cases. 12 | 13 | - ```user``` 14 | - ```group``` 15 | - ```device``` 16 | 17 | ## Alternatives to Adding Structural Properties 18 | 19 | Instead of adding a structural property to the existing core type (`user`, `group` or `device`), create a new type that models the information captured in the proposed structural property(s). 20 | Then, model the relationship between the existing core type and the new type by adding a navigation property. For information on modeling with navigation properties, see [Navigation Property](../patterns/navigation-property.md). 21 | 22 | ## Example: 23 | 24 | Modeling adding "bank account information", which includes two properties `accountNumber` and `routingNumber`, to entity type ```user```. 25 | 26 | ### Don't: 27 | 28 | Don't add new properties to core types such as `user`. 29 | 30 | ```xml 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | ### Do: 38 | 39 | Model the information by creating a new type and model the relationship to the existing core type with a navigation property. To determine which option is most appropriate, see [Navigation Property](../patterns/navigation-property.md): 40 | 41 | #### Option 1: Add a navigation property on the existing core type to the new type, containing the new type. 42 | 43 | Define the new entity type: 44 | ```xml 45 | 46 | 47 | 48 | 49 | ``` 50 | 51 | Add a contained navigation from user to the new entity type: 52 | ```xml 53 | 54 | 55 | 56 | ``` 57 | 58 | #### Option 2: Contain the new type in an entity set elsewhere, and add a navigation property to the new type on the existing core type. 59 | 60 | Define the new entity type: 61 | ```xml 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | Contain the new entity type in an entity set or singleton: 69 | ```xml 70 | 71 | ``` 72 | 73 | Add a navigation from user to the new type: 74 | ```xml 75 | 76 | 77 | 78 | ``` 79 | 80 | #### Option 3: Contain the new type in an entity set elsewhere, and add a navigation property to the existing core type on the new type. 81 | 82 | Define the new entity type, with a navigation to the user: 83 | ```xml 84 | 85 | 86 | 87 | 88 | 89 | ``` 90 | 91 | Contain the new entity type in an entity set or singleton: 92 | ```xml 93 | 94 | ``` 95 | -------------------------------------------------------------------------------- /graph/articles/deprecation.md: -------------------------------------------------------------------------------- 1 | # Deprecation guidelines 2 | 3 | If your API requires the introduction of breaking changes, you must add Revisions annotations to the API definition with the following terms: 4 | 5 | - **Date:** Date when the element was marked as deprecated. 6 | - **Version:** Used to organize the ChangeLog. Use the format "YYYY-MM/Category", where "YYYY-MM" is the month the deprecation is announced, and "Category" is the category under which the change is described. 7 | - **Kind:** Deprecated 8 | - **Description:** Human readable description of the change. Used in ChangeLog, documentation, etc. 9 | - **RemovalDate:** Earliest date when the element can be removed. 10 | 11 | The annotation can be applied to a type, an entity set, a singleton, a property, a 12 | navigation property, a function, or an action. If a type is marked as deprecated, it 13 | is not necessary to mark the members of that type as deprecated, nor is it necessary 14 | to annotate any usages of that type. 15 | 16 | ## Example of property annotation 17 | 18 | ```xml 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | When the request URL contains a reference to a deprecated model element, the gateway adds a [Deprecation header](https://tools.ietf.org/html/draft-dalal-deprecation-header-02) (with the date the element was marked as deprecated) and a Sunset header (with the date of two years beyond the deprecation date) to the response. 35 | 36 | ## Deprecation header example 37 | 38 | ``` 39 | Deprecation: Wed, 30 Mar 2022 11:59:59 GMT 40 | Sunset: Thursday, 30 June 2024 23:59:59 GMT 41 | Link: https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_name ; rel="deprecation"; type="text/html"; title="name",https://docs.microsoft.com/en-us/graph/changelog#2022-03-30_state ; rel="deprecation"; type="text/html"; title="state" 42 | 43 | ``` 44 | -------------------------------------------------------------------------------- /graph/articles/errorResponses.md: -------------------------------------------------------------------------------- 1 | # Error condition responses 2 | 3 | For non-success conditions, developers SHOULD be able to write one piece of code that handles errors consistently across different Microsoft REST API services. 4 | This allows building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses. 5 | The following is based on the OData v4 JSON spec. 6 | However, it is very generic and does not require specific OData constructs. 7 | APIs SHOULD use this format even if they are not using other OData constructs. 8 | 9 | The error response MUST be a single JSON object. 10 | This object MUST have a name/value pair named "error". The value MUST be a JSON object. 11 | 12 | This object MUST contain name/value pairs with the names "code" and "message", and it MAY contain name/value pairs with the names "target", "details" and "innererror." 13 | 14 | The value for the "code" name/value pair is a language-independent string and MUST match the HTTP response status code description, converted to camelCase, as listed in the [Status Code Registry (iana.org)](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) 15 | For example, if the HTTP status code is "Not Found", then the "code" value MUST be "notFound". 16 | 17 | Most services will require a larger number of more specific error codes, which are not interesting to all clients. 18 | These error codes SHOULD be exposed in the "innererror" name/value pair as described below. 19 | Introducing a new value for "code" that is visible to existing clients is a breaking change and requires a version increase. 20 | Services can avoid breaking changes by adding new error codes to "innererror" instead. 21 | 22 | The value for the "message" name/value pair MUST be a human-readable representation of the error. 23 | It is intended as an aid to developers and is not suitable for exposure to end users. 24 | Services wanting to expose a suitable message for end users MUST do so through an [odata-json-annotations](https://docs.oasis-open.org/odata/odata-json-format/v4.01/cs02/odata-json-format-v4.01-cs02.html#sec_AnnotateaJSONObject) or custom property. 25 | Services SHOULD NOT localize "message" for the end user, because doing so might make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet. 26 | 27 | The value for the "target" name/value pair is the target of the particular error (e.g., the name of the property in error). 28 | 29 | The value for the "details" name/value pair MUST be an array of JSON objects that MUST contain name/value pairs for "code" and "message", and MAY contain a name/value pair for "target", as described above. 30 | The objects in the "details" array usually represent distinct, related errors that occurred during the request. 31 | See example below. 32 | 33 | The value for the "innererror" name/value pair MUST be an object. 34 | The contents of this object are service-defined. 35 | Services wanting to return more specific errors than the root-level code MUST do so by including a name/value pair for "code" and a nested "innererror". Each nested "innererror" object represents a higher level of detail than its parent. 36 | When evaluating errors, clients MUST traverse through all of the nested "innererrors" and choose the deepest one that they understand. 37 | This scheme allows services to introduce new error codes anywhere in the hierarchy without breaking backwards compatibility, so long as old error codes still appear. 38 | The service MAY return different levels of depth and detail to different callers. 39 | For example, in development environments, the deepest "innererror" MAY contain internal information that can help debug the service. 40 | To guard against potential security concerns around information disclosure, services SHOULD take care not to expose too much detail unintentionally. 41 | Error objects MAY also include custom server-defined name/value pairs that MAY be specific to the code. 42 | Error types with custom server-defined properties SHOULD be declared in the service's metadata document. 43 | See example below. 44 | 45 | Error responses MAY contain Odata JSON annotations in any of their JSON objects. 46 | 47 | We recommend that for any transient errors that may be retried, services SHOULD include a Retry-After HTTP header indicating the minimum number of seconds that clients SHOULD wait before attempting the operation again. 48 | 49 | ## ErrorResponse : Object 50 | 51 | Property | Type | Required | Description 52 | -------- | ---- | -------- | ----------- 53 | `error` | Error | ✔ | The error object. 54 | 55 | ## Error : Object 56 | 57 | Property | Type | Required | Description 58 | -------- | ---- | -------- | ----------- 59 | `code` | String | ✔ | One of a server-defined set of error codes. 60 | `message` | String | ✔ | A human-readable representation of the error. 61 | `target` | String | | The target of the error. 62 | `details` | Error[] | | An array of details about specific errors that led to this reported error. 63 | `innererror` | InnerError | | An object containing more specific information than the current object about the error. 64 | 65 | ## InnerError : Object 66 | 67 | Property | Type | Required | Description 68 | -------- | ---- | -------- | ----------- 69 | `code` | String | | A more specific error code than was provided by the containing error. 70 | `innererror` | InnerError | | An object containing more specific information than the current object about the error. 71 | 72 | ## Examples 73 | 74 | Example of "innererror": 75 | 76 | ```json 77 | { 78 | "error": { 79 | "code": "unauthorized", 80 | "message": "Previous passwords may not be reused", 81 | "target": "password", 82 | "innererror": { 83 | "code": "passwordError", 84 | "innererror": { 85 | "code": "passwordDoesNotMeetPolicy", 86 | "minLength": "6", 87 | "maxLength": "64", 88 | "characterTypes": ["lowerCase","upperCase","number","symbol"], 89 | "minDistinctCharacterTypes": "2", 90 | "innererror": { 91 | "code": "passwordReuseNotAllowed" 92 | } 93 | } 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | In this example, the most basic error code is "unauthorized", but for clients that are interested, there are more specific error codes in "innererror." 100 | The "passwordReuseNotAllowed" code may have been added by the service at a later date, having previously only returned "passwordDoesNotMeetPolicy." 101 | Existing clients do not break when the new error code is added, but new clients MAY take advantage of it. 102 | The "passwordDoesNotMeetPolicy" error also includes additional name/value pairs that allow the client to determine the server's configuration, validate the user's input programmatically, or present the server's constraints to the user within the client's own localized messaging. 103 | 104 | Example of "details": 105 | 106 | ```json 107 | { 108 | "error": { 109 | "code": "badRequest", 110 | "message": "Multiple errors in ContactInfo data", 111 | "target": "contactInfo", 112 | "details": [ 113 | { 114 | "code": "nullValue", 115 | "target": "phoneNumber", 116 | "message": "Phone number must not be null" 117 | }, 118 | { 119 | "code": "nullValue", 120 | "target": "lastName", 121 | "message": "Last name must not be null" 122 | }, 123 | { 124 | "code": "malformedValue", 125 | "target": "address", 126 | "message": "Address is not valid" 127 | } 128 | ] 129 | } 130 | } 131 | ``` 132 | 133 | In this example there were multiple problems with the request, with each individual error listed in "details." 134 | -------------------------------------------------------------------------------- /graph/articles/filter-as-segment.md: -------------------------------------------------------------------------------- 1 | # Filter as segment 2 | 3 | There is an [OData feature](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_AddressingaSubsetofaCollection) which allows having a `$filter` in a URL segment. 4 | This feature is useful whenever there are operations on a collection and the client wants to perform those operations on a *subset* of the collection. 5 | For example, the `riskyUsers` API on Microsoft Graph has an action defined to let clients "dismiss" risky users (i.e. consider those users "not risky"): 6 | 7 | ```xml 8 | 9 | 10 | 11 | 12 | ``` 13 | 14 | Using this action, clients can call 15 | 16 | ```http 17 | POST /identityProtection/riskyUsers/dismiss 18 | { 19 | "userIds": [ 20 | "{userId1}", 21 | "{userId2}", 22 | ... 23 | ] 24 | } 25 | ``` 26 | 27 | in order to dismiss the risky users with the provided IDs. Using the filter-as-segment OData feature, the action could instead be defined as: 28 | 29 | ```xml 30 | 31 | 32 | 33 | ``` 34 | 35 | and clients could call 36 | 37 | ```http 38 | POST /identityProtection/riskyUsers/$filter=@f/dismiss?@f=id IN ('{userId1}','{userId2}',...) 39 | ``` 40 | 41 | Doing this is beneficial due to the robust nature of OData filter expressions: clients will be able to dismiss risky users based on any supported filter without the service team needing to implement a new `dismiss` overload that filters based on the new criteria. 42 | However, there are some concerns about the discoverability of using the filter-as-segment feature, as well as the support of [parameter aliasing](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_ParameterAliases) that's required. 43 | As a result, functions should be introduced that act in the same way as the filter-as-segment: 44 | 45 | ```xml 46 | 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | Clients would now be able to call 54 | 55 | ```http 56 | POST /identityProtection/riskyUsers/filter(expression='id IN (''{userId1}'',''{userId2}'',...)')/dismiss 57 | ``` 58 | 59 | NOTE: the `'` literal in the filter expression must be escaped with `''` 60 | 61 | An example implementation of a filter function using OData WebApi can be found [here](https://github.com/OData/AspNetCoreOData/commit/7732f7e6b812d9a79a73529562f2e74b68e2794f). 62 | -------------------------------------------------------------------------------- /graph/articles/naming.md: -------------------------------------------------------------------------------- 1 | # Naming 2 | 3 | ## 1. Approach 4 | 5 | Naming policies should aid developers in discovering functionality without having to constantly refer to documentation. 6 | Use of common patterns and standard conventions greatly aids developers in correctly guessing common property names and meanings. 7 | Services SHOULD use verbose naming patterns and MUST NOT use abbreviations other than acronyms that are the dominant mode of expression in the domain being represented by the API, (e.g. Url). 8 | 9 | ## 2. Casing 10 | 11 | - Acronyms SHOULD follow the casing conventions as though they were regular words (e.g. Url). 12 | - All identifiers including namespaces, entityTypes, entitySets, properties, actions, functions and enumeration values MUST use lowerCamelCase. 13 | - HTTP headers are the exception and SHOULD use standard HTTP convention of Capitalized-Hyphenated-Terms. 14 | 15 | ## 3. Names to avoid 16 | 17 | Certain names are so overloaded in API domains that they lose all meaning or clash with other common usages in domains that cannot be avoided when using REST APIs, such as OAUTH. 18 | Services SHOULD NOT use the following names: 19 | 20 | - Context 21 | - Scope 22 | - Resource 23 | 24 | ## 4. Forming compound names 25 | 26 | - Services SHOULD avoid using articles such as 'a', 'the', 'of' unless needed to convey meaning. 27 | - e.g. names such as aUser, theAccount, countOfBooks SHOULD NOT be used, rather user, account, bookCount SHOULD be preferred. 28 | - Services SHOULD add a type to a property name when not doing so would cause ambiguity about how the data is represented or would cause the service not to use a common property name. 29 | - When adding a type to a property name, services MUST add the type at the end, e.g. createdDateTime. 30 | 31 | ## 5. Identity properties 32 | 33 | - Services MUST use string types for identity properties. 34 | - For OData services, the service MUST use the OData @id property to represent the canonical identifier of the resource. 35 | - Services MAY use the simple 'id' property to represent a local or legacy primary key value for a resource. 36 | - Services SHOULD use the name of the relationship postfixed with 'Id' to represent a foreign key to another resource, e.g. subscriptionId. 37 | - The content of this property SHOULD be the canonical ID of the referenced resource. 38 | 39 | ## 6. Date and time properties 40 | 41 | - For properties requiring both date and time, services MUST use the suffix 'DateTime'. 42 | - For properties requiring only date information without specifying time, services MUST use the suffix 'Date', e.g. birthDate. 43 | - For properties requiring only time information without specifying date, services MUST use the suffix 'Time', e.g. appointmentStartTime. 44 | 45 | ## 7. Name properties 46 | 47 | - For the overall name of a resource typically shown to users, services MUST use the property name 'displayName'. 48 | - Services MAY use other common naming properties, e.g. givenName, surname, signInName. 49 | 50 | ## 8. Collections and counts 51 | 52 | - Services MUST name collections as plural nouns or plural noun phrases using correct English. 53 | - Services MAY use simplified English for nouns that have plurals not in common verbal usage. 54 | - e.g. schemas MAY be used instead of schemata. 55 | - Services MUST name counts of resources with a noun or noun phrase suffixed with 'Count'. 56 | 57 | ## 9. Common property names 58 | 59 | Where services have a property, whose data matches the names below, the service MUST use the name from this table. 60 | This table will grow as services add terms that will be more commonly used. 61 | Service owners adding such terms SHOULD propose additions to this document. 62 | 63 | | | | 64 | |-------------------- | - | 65 | attendees | 66 | body | 67 | completedDateTime | **NOTE** completionDateTime may be used for cases where the timestamp represents a point in the future | 68 | createdDateTime | 69 | childCount | 70 | children | 71 | contentUrl | 72 | country | 73 | createdBy | 74 | displayName | 75 | errorUrl | 76 | eTag | 77 | event | 78 | expirationDateTime | 79 | givenName | 80 | jobTitle | 81 | kind | 82 | id | 83 | lastModifiedDateTime | 84 | location | 85 | memberOf | 86 | message | 87 | name | 88 | owner | 89 | people | 90 | person | 91 | postalCode | 92 | photo | 93 | preferredLanguage | 94 | properties | 95 | signInName | 96 | surname | 97 | tags | 98 | userPrincipalName | 99 | webUrl | 100 | -------------------------------------------------------------------------------- /graph/articles/nullable.md: -------------------------------------------------------------------------------- 1 | # Nullable Properties 2 | 3 | A nullable property means *only* that the property may have `null` as a value; the "nullability" of a property does not say anything about how a value is set into a property. 4 | For example, a non-nullable property is *not* required to create a new instance of an entity. 5 | It only means that the property will have a value when it is retrieved. 6 | In the case that no value is provided when the entity is created, this means that the service will create one; this value can be specified with the `DefaultValue` attribute, but if the value is contextual and determine at request time, then the property can both be non-nullable *and* have no `DefaultValue` specified. 7 | Below are some examples of nullable and non-nullable properties. 8 | 9 | ## CSDL 10 | 11 | ```xml 12 | 13 | ... 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ... 24 | 25 | ``` 26 | 27 | ## HTTP Requests 28 | 29 | ### 1. Create a servicePrincipal with no properties 30 | 31 | ```HTTP 32 | POST /servicePrincipals 33 | 34 | 400 Bad Request 35 | { 36 | "error": { 37 | "code": "badRequest", 38 | "message": "The 'appId' property is required to create a servicePrincipal." 39 | } 40 | } 41 | ``` 42 | 43 | ### 2. Create a servicePrincipal without a display name 44 | 45 | ```HTTP 46 | POST /servicePrincipals 47 | { 48 | "appId": "00000000-0000-0000-0000-000000000001" 49 | } 50 | 51 | 201 Created 52 | { 53 | "appId": "00000000-0000-0000-0000-000000000001", 54 | "displayName": "some application name", 55 | "foo": "testval", 56 | "bar": "differentvalue", 57 | ... 58 | } 59 | ``` 60 | Notes: 61 | 1. `displayName` was given a value by the service even though no value was provided by the client 62 | 2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL 63 | 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL 64 | 65 | ### 3. Update the display name of a service principal to null 66 | 67 | ```HTTP 68 | PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 69 | { 70 | "displayName": null 71 | } 72 | 73 | 400 Bad Request 74 | { 75 | "error": { 76 | "code": "badRequest", 77 | "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." 78 | } 79 | } 80 | ``` 81 | Notes: 82 | 1. `displayName` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. 83 | 84 | ### 4. Update the display name of a service principal 85 | 86 | ```HTTP 87 | PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 88 | { 89 | "displayName": "a non-generated display name" 90 | } 91 | 92 | 200 OK 93 | { 94 | "appId": "00000000-0000-0000-0000-000000000001", 95 | "displayName": "a non-generated display name", 96 | "foo": "testval", 97 | "bar": "differentvalue", 98 | ... 99 | } 100 | ``` 101 | Notes: 102 | 1. `displayName` can be set to any value other than `null` 103 | 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. 104 | 105 | ### 5. Update the foo property of a service principal to null 106 | 107 | ```HTTP 108 | PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 109 | { 110 | "foo": null 111 | } 112 | 113 | 200 OK 114 | { 115 | "appId": "00000000-0000-0000-0000-000000000001", 116 | "displayName": "a non-generated display name", 117 | "foo": null, 118 | "bar": "differentvalue", 119 | ... 120 | } 121 | ``` 122 | Notes: 123 | 1. `foo` can be set to `null` because it has be marked with `Nullable="true"` in the CSDL. 124 | 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. 125 | 126 | ### 6. Update the foo property of a service principal to a non-default value 127 | 128 | ```HTTP 129 | PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 130 | { 131 | "foo": "something other than testval" 132 | } 133 | 134 | 200 OK 135 | { 136 | "appId": "00000000-0000-0000-0000-000000000001", 137 | "displayName": "a non-generated display name", 138 | "foo": "something other than testval", 139 | "bar": "differentvalue", 140 | ... 141 | } 142 | ``` 143 | Notes: 144 | 1. `foo` can be set to `something other than testval` 145 | 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. 146 | 147 | ### 7. Update the bar property of a service principal to null 148 | 149 | ```HTTP 150 | PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 151 | { 152 | "bar": null 153 | } 154 | 155 | 400 Bad Request 156 | { 157 | "error": { 158 | "code": "badRequest", 159 | "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." 160 | } 161 | } 162 | ``` 163 | Notes: 164 | 1. `bar` cannot be set to `null` because it has be marked with `Nullable="false"` in the CSDL. 165 | 166 | ### 8. Update the bar property of a service principal to a non-default value 167 | 168 | ```HTTP 169 | PATCH /servicePrincipals/00000000-0000-0000-0000-000000000001 170 | { 171 | "bar": "a new bar" 172 | } 173 | 174 | 200 OK 175 | { 176 | "appId": "00000000-0000-0000-0000-000000000001", 177 | "displayName": "a non-generated display name", 178 | "foo": "something other than testval", 179 | "bar": "a new bar", 180 | ... 181 | } 182 | ``` 183 | Notes: 184 | 1. `bar` can be set to `a new bar` 185 | 2. The response body here is provided for clarity, and is not part of the guidance itself. The [OData v4.01 standard](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) states that the workload can decide the behavior. 186 | 187 | ### 9. Create a service principal while customizing the display name 188 | ```HTTP 189 | POST /servicePrincipals 190 | { 191 | "appId": "00000000-0000-0000-0000-000000000001", 192 | "displayName": "a different name" 193 | } 194 | 195 | 201 Created 196 | { 197 | "appId": "00000000-0000-0000-0000-000000000001", 198 | "displayName": "a different name", 199 | "foo": "testval", 200 | "bar": "differentvalue", 201 | ... 202 | } 203 | ``` 204 | Notes: 205 | 1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. 206 | 2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL 207 | 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL 208 | 209 | ### 10. Create a service principal with a null display name 210 | ```HTTP 211 | POST /servicePrincipals 212 | { 213 | "appId": "00000000-0000-0000-0000-000000000001", 214 | "displayName": null 215 | } 216 | 217 | 400 Bad Request 218 | { 219 | "error": { 220 | "code": "badRequest", 221 | "message": "null is not a valid value for the property 'displayName'; 'displayName' is not a nullable property." 222 | } 223 | } 224 | ``` 225 | Notes: 226 | 1. `displayName` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` 227 | 228 | ### 11. Create a service principal with a value for the foo property 229 | ```HTTP 230 | POST /servicePrincipals 231 | { 232 | "appId": "00000000-0000-0000-0000-000000000001", 233 | "foo": "a foo value on creation" 234 | } 235 | 236 | 201 Created 237 | { 238 | "appId": "00000000-0000-0000-0000-000000000001", 239 | "displayName": "some application name", 240 | "foo": "a foo value on creation", 241 | "bar": "differentvalue", 242 | ... 243 | } 244 | ``` 245 | Notes: 246 | 1. `displayName` was given a value by the service even though no value was provided by the client 247 | 2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. 248 | 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL 249 | 250 | ### 12. Create a service principal with null for the foo property 251 | ```HTTP 252 | POST /servicePrincipals 253 | { 254 | "appId": "00000000-0000-0000-0000-000000000001", 255 | "foo": null 256 | } 257 | 258 | 201 Created 259 | { 260 | "appId": "00000000-0000-0000-0000-000000000001", 261 | "displayName": "some application name", 262 | "foo": null, 263 | "bar": "differentvalue", 264 | ... 265 | } 266 | ``` 267 | Notes: 268 | 1. `displayName` was given a value by the service even though no value was provided by the client 269 | 2. `foo` isn't required to create a new `servicePrincipal`, but it *can* be provided; because the property has `Nullable="true"`, a `null` value can be provided for it. 270 | 3. `bar` has the default value as specified by its `DefaultValue` attribute in the CSDL 271 | 272 | ### 13. Create a service principal with a value for the bar property 273 | ```HTTP 274 | POST /servicePrincipals 275 | { 276 | "appId": "00000000-0000-0000-0000-000000000001", 277 | "bar": "running out of ideas for value names" 278 | } 279 | 280 | 201 Created 281 | { 282 | "appId": "00000000-0000-0000-0000-000000000001", 283 | "displayName": "some application name", 284 | "foo": "testval", 285 | "bar": "running out of ideas for value names", 286 | ... 287 | } 288 | ``` 289 | Notes: 290 | 1. `displayName` was given a value by the service even though no value was provided by the client 291 | 2. `foo` has the default value as specified by its `DefaultValue` attribute in the CSDL 292 | 3. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; this is orthogonal to whether or not the property has `Nullable="true"` or `Nullable="false"`. 293 | 294 | ### 14. Create a service principal with null for the bar property 295 | ```HTTP 296 | POST /servicePrincipals 297 | { 298 | "appId": "00000000-0000-0000-0000-000000000001", 299 | "bar": null 300 | } 301 | 302 | 400 Bad Request 303 | { 304 | "error": { 305 | "code": "badRequest", 306 | "message": "null is not a valid value for the property 'bar'; 'bar' is not a nullable property." 307 | } 308 | } 309 | ``` 310 | Notes: 311 | 1. `bar` isn't required to create a new `servicePrincipal`, but it *can* be provided; it *cannot* be provided as `null` because the property was marked with `Nullable="false"` 312 | -------------------------------------------------------------------------------- /graph/patterns/LRO.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/api-guidelines/3db3462a9361132adf60da74cff3aced08eb9ff7/graph/patterns/LRO.gif -------------------------------------------------------------------------------- /graph/patterns/PatternDescriptionTemplate.md: -------------------------------------------------------------------------------- 1 | # Pattern name 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *Provide a short description of the pattern.* 6 | 7 | 8 | ## Problem 9 | 10 | *Describe the business context relevant for the pattern.* 11 | 12 | *Provide a short description of the problem.* 13 | 14 | ## Solution 15 | 16 | *Describe how to implement the solution to solve the problem.* 17 | 18 | *Describe related patterns.* 19 | 20 | ## When to use this pattern 21 | 22 | *Describe when and why the solution is applicable and when it might not be.* 23 | 24 | ## Issues and considerations 25 | 26 | *Describe tradeoffs of the solution.* 27 | 28 | ## Example 29 | 30 | *Provide a short example from real life.* 31 | -------------------------------------------------------------------------------- /graph/patterns/RELO.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/api-guidelines/3db3462a9361132adf60da74cff3aced08eb9ff7/graph/patterns/RELO.gif -------------------------------------------------------------------------------- /graph/patterns/alternate-key.md: -------------------------------------------------------------------------------- 1 | # Alternate key 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The alternate key pattern provides the ability to query for a single, specific resource identifiable via one of an alternative set of properties that is not its primary key.* 6 | 7 | ## Problem 8 | 9 | The resources exposed in Microsoft Graph are identified through a primary key, which guarantees uniqueness inside the same resource collection. Often though, that same resource can also be uniquely identified by an alternative, more convenient property that provides a better developer experience. 10 | 11 | Take a look at the `user` resource: while the `id` is the typical way to get the resource details, the `mail` address is also a unique property that can be used to identify it. 12 | 13 | The resource can be accessed using the `$filter` query parameter, such as 14 | 15 | ```http 16 | GET https://graph.microsoft.com/v1.0/users?$filter=mail eq 'bob@contoso.com' 17 | ``` 18 | However, in this case, the returned result is wrapped in an array that needs to be unpacked. When the uniqueness of the property within the collection implies that only zero or one results can be returned from the call this array provides a suboptimal experience for callers. 19 | 20 | ## Solution 21 | 22 | Typically resources in Graph are accessed using a simple forward-slash delimited URL pattern (this pattern is sometimes referred to as key-as-segment). 23 | 24 | ```http 25 | https://graph.microsoft.com/v1.0/users/0 - Retrieves the employee with ID = 0. 26 | ``` 27 | 28 | However, resources can also be accessed using parentheses to delimit the key, like this: 29 | 30 | ```http 31 | https://graph.microsoft.com/v1.0/users(0) - Also retrieves the employee with ID = 0. 32 | ``` 33 | 34 | Resource addressing by using an alternative key can be achieved by using this same parentheses-style convention with one difference: alternate keys MUST specify the key property name to unambiguously determine the alternate key, like this: 35 | 36 | ```http 37 | https://graph.microsoft.com/v1.0/users(email='bob@contoso.com') Retrieves the employee with the email matching `bob@contoso.com`. 38 | ``` 39 | 40 | In the same way as requesting a resource via the canonical key, if a resource cannot be located that matches the alternate key, then a 404 must be returned. 41 | 42 | > **Note:** When requesting a resource via alternate keys, the simple slash-delimited URL style does not work. 43 | 44 | > **Note:** Do not use multi-part alternate keys. Feedback has been that customers find multi-part keys confusing. 45 | > Either create a composite single-part surrogate key property or fall back to logical operations in a $filter clause. 46 | 47 | ## When to use this pattern 48 | 49 | Use this pattern when your resource type has other keys than its canonical key which uniquely identify a single resource. 50 | 51 | ## Example 52 | 53 | The same user is identified via the alternate key SSN, the canonical (primary) key ID using the non-canonical long form with a specified key property name, and the canonical short form without a key property name. 54 | 55 | Declare `mail` and `ssn` as alternate keys on an entity: 56 | 57 | ```xml 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ``` 90 | 91 | 1. Get a specific resource through `$filter`: 92 | 93 | ```http 94 | GET https://graph.microsoft.com/v1.0/users/?$filter=ssn eq '123-45-6789' 95 | ``` 96 | 97 | ```json 98 | { 99 | "value": [ 100 | { 101 | "givenName": "Bob", 102 | "jobTitle": "Retail Manager", 103 | "mail": "bob@contoso.com", 104 | "mobilePhone": "+1 425 555 0109", 105 | "officeLocation": "18/2111", 106 | "preferredLanguage": "en-US", 107 | "ssn": "123-45-6789", 108 | "surname": "Vance", 109 | "userPrincipalName": "bob@contoso.com", 110 | "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" 111 | } 112 | ] 113 | } 114 | ``` 115 | 116 | 2. Get a specific resource either through its primary key or through the two alternate keys: 117 | 118 | ```http 119 | GET https://graph.microsoft.com/v1.0/users/1a89ade6-9f59-4fea-a139-23f84e3aef66 120 | GET https://graph.microsoft.com/v1.0/users(1a89ade6-9f59-4fea-a139-23f84e3aef66) 121 | GET https://graph.microsoft.com/v1.0/users(ssn='123-45-6789') 122 | GET https://graph.microsoft.com/v1.0/users(mail='bob@contoso.com') 123 | ``` 124 | 125 | All four yield the same response: 126 | 127 | ```json 128 | { 129 | "givenName": "Bob", 130 | "jobTitle": "Retail Manager", 131 | "mail": "bob@contoso.com", 132 | "mobilePhone": "+1 425 555 0109", 133 | "officeLocation": "18/2111", 134 | "preferredLanguage": "en-US", 135 | "ssn": "123-45-6789", 136 | "surname": "Vance", 137 | "userPrincipalName": "bob@contoso.com", 138 | "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66" 139 | } 140 | ``` 141 | 142 | 3. Request a resource for an unsupported alternate key property: 143 | 144 | ```http 145 | GET https://graph.microsoft.com/v1.0/users(name='Bob') 146 | 147 | 400 Bad Request 148 | { 149 | "error" : { 150 | "code" : "400", 151 | "message": "'name' is not a valid alternate key for the resource type 'user'." 152 | } 153 | } 154 | ``` 155 | 156 | 4. Request a resource where the alternate key property does not exist on any resource in the collection: 157 | 158 | ```http 159 | GET https://graph.microsoft.com/v1.0/users(email='unknown@contoso.com') 160 | 161 | 404 Not Found 162 | { 163 | "error" : { 164 | "code" : "404", 165 | "message": "No user with the the specified 'email' could be found." 166 | } 167 | } 168 | ``` 169 | -------------------------------------------------------------------------------- /graph/patterns/antiPatternTemplate.md: -------------------------------------------------------------------------------- 1 | 2 | # Antipattern name 3 | 4 | *name with a negative connotation* 5 | 6 | *Example: Flatbag of properties* 7 | 8 | ## Description 9 | 10 | *Example: The flat bag pattern is a known anti-pattern in Microsoft Graph, where multiple variants of a common concept are modeled as a single entity type with all potential properties plus an additional property to distinguish the variants.* 11 | 12 | ## Consequences 13 | *Describe the consequences in terms of the developer experience* 14 | 15 | *Example: This is the least recommended modeling choice because it is weakly typed, which increases the number of variations and complexity of solutions, making it difficult to verify the semantic correctness of the API for both clients and producers...* 16 | 17 | ## Preferable solutions 18 | 19 | *Example: It is preferable to use type hierarchy and facets patterns. 20 | Should provide a link to a valid pattern or patterns.* 21 | 22 | ## Example 23 | 24 | *Provide an example of better modeling* 25 | -------------------------------------------------------------------------------- /graph/patterns/change-tracking.md: -------------------------------------------------------------------------------- 1 | # Change tracking 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The change tracking pattern provides the ability for API consumers to request changes in data from Microsoft Graph without having to re-read data that has not changed.* 6 | 7 | 8 | ## Problem 9 | 10 | API consumers require an efficient way to acquire changes to data in the Microsoft Graph, for example to synchronize an external store or to drive a change-centric business process. 11 | 12 | ## Solution 13 | 14 | API designers can enable the change tracking (delta) capability on a resource in the Microsoft Graph (typically on an entity collection or a parent resource) by declaring a delta function on that resource and applying `Org.OData.Capabilities.V1.ChangeTracking` annotation. 15 | 16 | This function returns a delta payload. A delta payload consists of a collection of annotated full or partial Microsoft Graph entities plus either a `nextLink` to further pages of original or change data that are immediately available OR a `deltaLink` to get the next set of changes at some later date. 17 | 18 | The `nextLink` provides a mechanism to do server-driven paging through the change data that is currently available. When there are no further pages of changes immediately available, a `deltaLink` is returned instead. 19 | The `deltaLink` provides a mechanism for the API consumer to catch up on changes since their last request to the delta function. If no changes have happened since the last request, then the deltaLink MUST return an empty collection. 20 | 21 | Both `nextLink` and `deltaLink` MUST be considered opaque URLs. The best practice is to make them opaque via encoding. 22 | 23 | The pattern requires a sequence of requests on the delta function, for additional details see [Change Tracking](https://learn.microsoft.com/en-us/graph/delta-query-overview?tabs=http#use-delta-query-to-track-changes-in-a-resource-collection): 24 | 25 | 1. GET request which returns the first page of the current state of the resources that delta applies to. 26 | 2. [Optionally] Further GET requests to retrieve more pages of the current state via the `@odata.nextLink` URL. 27 | 3. After some time, a GET request to see if there are new changes via the `@odata.deltaLink` URL. 28 | 4. [Optionally] GET requests to retrieve more pages of changes via the `@odata.nextLink` URL. 29 | 30 | Delta payload requirements: 31 | - The payload is a collection of change records using the collection format. 32 | - The change records are full or partial representations of the resources according to their resource types. 33 | - When a change representing a resource update is included in the payload the API producer MAY return either the changed properties or the full entity. The ID of the resource MUST be included in every change record. 34 | - When an entity is deleted, the delta function MUST return the ID of the deleted entity as well as an `@removed` annotation with the reason field. 35 | - When an entity is deleted, the reason MUST be set to “changed” if the entity can be restored. 36 | - When an entity is deleted. the reason MUST be set to “deleted” if the entity cannot be restored. 37 | - There is no mechanism to indicate that a resource has entered or exited the dataset based on a change that causes it to match or no longer match any `$filter` query parameter. 38 | - When a link to an entity is deleted, when the linked entity is deleted, or when a link to an entity is added, the implementer MUST return a `property@delta` annotation. 39 | - When a link to an entity is deleted, but the entity still exists, the reason MUST be set to `changed`. 40 | - When a link to an entity is deleted along with the entity, the reason MUST be set to `deleted`. 41 | 42 | API producers MAY choose to collate multiple changes to the same resource into a single change record. 43 | 44 | API consumers are expected to differentiate resource adds from updates by interpreting the id property of the change records against the existence of resources in whatever external system is doing the processing. 45 | 46 | 47 | ## When to use this pattern 48 | 49 | API consumers want a pull mechanism to request and process change to Microsoft Graph data, either via proactive polling or by responding to Microsoft Graph notifications. 50 | 51 | API consumers need guaranteed data integrity over the set of changes to Microsoft Graph data. 52 | 53 | ## Considerations 54 | 55 | - API service MAY be able to respond to standard OData query parameters with the initial call to the delta function: 56 | 57 | - `$select` to enforce the set of properties on which change is reported. 58 | - `$filter` to influence the scope of changes returned. 59 | - `$expand` to include linked resources with the set of changes. 60 | - `$top` parameter to influence the size of the set of change records. 61 | 62 | These query parameters MUST be encoded into subsequent `@odata.nextLink` or `@odata.deltaLink`, such that the same options are preserved through the call sequence without callers respecifying them, which MUST NOT be allowed. OData query parameters must be honored in full, or a 400-error returned. 63 | - Making a sequence of calls to a delta function followed by the opaque URLs in the `nextLink` and `deltaLink` MUST guarantee that the data at the start time of the call sequence and all changes to the data thereafter will be returned at least once. It is not necessary to avoid duplicates in the sequence. When the delta function is returning changes, they MUST be sequenced chronologically refer to [public documentation](https://learn.microsoft.com/en-us/graph/delta-query-overview?view=graph-rest-1.0) for more details. 64 | - The delta function can be bound to 65 | - an entity collection, as with `/users/delta` that returns the changes to the users' collection, or 66 | - some logical parent resource that returns an entity collection, where the change records are implied to be relative to all collections contained within the parent.For example `/me/planner/all/delta` returns changes to any resource within a planner, which are referenced by 'all' navigation property, and `/communications/onlineMeetings/getAllRecordings/delta` returns changes to any meeting recordings returned by `getAllRecordings` function. 67 | 68 | - API service should use `$skipToken` and `$deltaToken` within their implementations of `nextLink` and `deltaLink`, however the URLs are defined as being opaque and the existence of the tokens MUST NOT be documented. It is not a breaking change to modify the structure of `nextLinks` or `deltaLinks`.- 69 | - `nextLink` and `deltaLink` URLs are valid for a specific period before the client application needs to run a full synchronization again.For `nextLink`, a minimal validity time should be 1 hour. For `deltaLink`, a minimal validity time should be seven days. When a link is no longer valid it must return a standard error with a 410 GONE response code. 70 | - Although this capability is similar to the OData `$delta` feed capability, it is a different construct. Microsoft Graph APIs MUST provide change tracking through the delta function and MUST NOT implement the OData `$delta` feed when providing change tracking capabilities to ensure the uniformity of the API experience. 71 | - The Graph delta payload format has some deviations from the OData 4.01 change tracking format to simplify parsing, for example the context annotation is removed. 72 | - Additional implementation details are documented [internally](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/211718/Deltas). 73 | 74 | 75 | ## Alternatives 76 | 77 | - Change notifications pattern with rich payloads – for use cases where API consumers would find calling back into Microsoft Graph onerous and absolute integrity guarantees are less critical. 78 | 79 | 80 | ## Examples 81 | 82 | ### Change tracking on entity set 83 | 84 | ```xml 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ``` 97 | 98 | ### Change tracking on navigation property 99 | 100 | ```xml 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | ``` 120 | ### Change tracking on function that return an entity collection 121 | 122 | Firstly, an API designer needs to define the function as composable (so that a delta function can be added to it), by adding the `IsComposable` annotation: 123 | 124 | ```xml 125 | 126 | 127 | 128 | 129 | ``` 130 | 131 | Next, define the `delta` function. The binding parameter and the return type of the delta function MUST be the same as the return type of the target `getAllRecordings` function: 132 | 133 | ```xml 134 | 135 | 136 | 137 | 138 | ``` 139 | Finally, for the function, the designer needs to add an annotation (either as a child of the entity or by targeting the entity type as below) stating that it supports change tracking (delta query): 140 | 141 | ```xml 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | ``` 150 | Here is the HTTP request to start the change tracking process on `getAllRecordings` 151 | 152 | ```http 153 | GET https://graph.microsoft.com/v1.0/communications/onlineMeetings/getAllRecordings/delta 154 | ``` 155 | 156 | ### Delta payload 157 | 158 | Here after the initial delta call, a user resource is updated, and there is one user added to and one removed from that user’s directReports collection. Additionally, a second user is deleted. In this case, there are no further pages of change records currently available. For detailed sequence of requests see [Change Tracking](https://learn.microsoft.com/en-us/graph/delta-query-overview?tabs=http#use-delta-query-to-track-changes-in-a-resource-collection). 159 | 160 | ```http 161 | GET https://graph.microsoft.com/v1.0/users/delta?$skiptoken=pqwSUjGYvb3jQpbwVAwEL7yuI3dU1LecfkkfLPtnIjvB7XnF_yllFsCrZJ 162 | 163 | { 164 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", 165 | "@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?$deltatoken=mS5DuRZGjVL-abreviated", 166 | "value": [ 167 | { 168 | "businessPhones": ["+1 309 555 0104"], 169 | "displayName": "Grady Archie", 170 | "givenName": "Grady", 171 | "jobTitle": "Designer", 172 | "mail": "GradyA@contoso.onmicrosoft.com", 173 | "officeLocation": "19/2109", 174 | "preferredLanguage": "en-US", 175 | "surname": "Archie", 176 | "userPrincipalName": "GradyA@contoso.onmicrosoft.com", 177 | "id": "0baaae0f-b0b3-4645-867d-742d8fb669a2", 178 | "directReports@delta": [ 179 | { 180 | "@odata.type": "#microsoft.graph.user", 181 | "id": "99789584-a1e1-4232-90e5-866170e3d4e7" 182 | } , 183 | { 184 | "id": "66789583-f1e2-6232-70e5-366170e3d4a6", 185 | "@removed": { 186 | "reason": "deleted" 187 | } 188 | } 189 | ] 190 | }, 191 | { 192 | "id": "0bbbbb0f-b0b3-4645-867d-742d8fb669a2", 193 | "@removed": { 194 | "reason": "changed" 195 | } 196 | } 197 | ] 198 | } 199 | ``` 200 | -------------------------------------------------------------------------------- /graph/patterns/default-properties.md: -------------------------------------------------------------------------------- 1 | # Default properties 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The default properties pattern allows API producers to omit specific properties from the response unless they are explicitly requested using `$select`.* 6 | 7 | ## Problem 8 | 9 | API designers want to control the set of properties that their entities return by default, when the incoming request does not specify a `$select`. This can be desirable when an entity type has many properties or an API producer needs to add properties that are computationally expensive to return by default. 10 | 11 | ## Solution 12 | 13 | For incoming requests targeting an entity type where the caller does not specify a `$select` clause, API producers **may** return a subset of the entity type's properties, omitting computationally expensive properties. To get the non-default properties of an entity type, callers must explicitly request them using `$select`. 14 | 15 | The pattern also uses an instance annotation to inform callers that other properties are also available. The same annotation is also used to encourage callers to use `$select`. 16 | 17 | ## When to use this pattern 18 | 19 | API producers should use this pattern when adding expensive or non-performant properties to an existing entity type, or when adding properties to an entity type that has already grown too large (with more than 20 properties). 20 | 21 | ## Issues and considerations 22 | 23 | - Do **not** rely on the `ags:Default` schema annotation for default properties functionality, as this is a legacy implementation. Returning default properties **must** be implemented by API producers. 24 | - Changing a default property to non-default is considered a breaking change. 25 | - One of the challenges with default properties is informing developers that the response does not contain the full set of properties. To solve for this discovery problem, if the response contains default properties only, then: 26 | - the response **must** contain a `@microsoft.graph.tips` instance annotation. 27 | - the `@microsoft.graph.tips` instance annotation **must** only be emitted if the client uses "developer mode" via the `Prefer: ms-graph-dev-mode` HTTP request header. It is expected that this header will only be used by client developer and scripting tools like Graph Explorer, the Microsoft Graph Postman collections, and Microsoft Graph PowerShell. 28 | - the `@microsoft.graph.tips` instance annotation value **must** contain "This request only returns a subset of the resource's properties. Your app will need to use $select to return non-default properties. To find out what other properties are available for this resource see https://learn.microsoft.com/graph/api/resources/{entityTypeName}". 29 | - Callers must be able to use `$filter` with non-default properties, even though they won't show up by default in the response. 30 | 31 | Additionally, for incoming requests targeting an entity type where the caller does not specify a `$select` clause, the API Gateway Service will inject a `@microsoft.graph.tips` instance annotation, informing callers to use $select, when in "developer mode". 32 | API producers who use [response passthrough](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/391069/Enabling-response-passthrough) must also implement this behavior, supplying the same information as shown in the [examples section below](#calling-an-api-without-using-select). 33 | 34 | ## Examples 35 | 36 | In this example we'll use the following `channel` entity type. 37 | 38 | ```xml 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | In this scenario, the API producer wants to add the `moderationSettings` property to the `channel` entity type. 54 | But when paging through 1000 channels at a time, this additional property will introduce a considerable increase in the response times. 55 | The API producer will use the default properties pattern here, and **not** return `moderationSettings` by default. 56 | 57 | ### Calling an API with default properties 58 | 59 | In this example, the caller, using Graph Explorer, does not use $select, and the API returns just the default properties. 60 | 61 | #### Request 62 | 63 | ```http 64 | GET /teams/{id}/channels 65 | Prefer: ms-graph-dev-mode 66 | ``` 67 | 68 | #### Response 69 | 70 | ```http 71 | 200 ok 72 | Content-type: application/json 73 | ``` 74 | 75 | ```json 76 | { 77 | "@odata.context": "https://graph.microsoft.com/beta/$metadata#Collection(microsoft.graph.channel)", 78 | "@microsoft.graph.tips": "This request only returns a subset of the resource properties. Your app will need to use $select to return non-default properties. To find out what other properties are supported for this resource, please see the Properties section in https://learn.microsoft.com/graph/api/resources/channel.", 79 | "value": [ 80 | { 81 | "displayName": "My First Shared Channel", 82 | "description": "This is my first shared channels", 83 | "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk1@thread.tacv2", 84 | "membershipType": "shared", 85 | "email": "someemail@dot.com", 86 | "webUrl": "webUrl-value", 87 | "filesFolderWebUrl": "sharePointUrl-value", 88 | "tenantId": "tenantId-value", 89 | "isFavoriteByDefault": null, 90 | "createdDateTime": "2019-08-07T19:00:00Z" 91 | }, 92 | { 93 | "displayName": "My Second Private Channel", 94 | "description": "This is my second shared channels", 95 | "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk2@thread.tacv2", 96 | "membershipType": "private", 97 | "email": "someemail2@dot.com", 98 | "webUrl": "webUrl-value2", 99 | "filesFolderWebUrl": "sharePointUrl-value2", 100 | "tenantId": "tenantId-value", 101 | "isFavoriteByDefault": null, 102 | "createdDateTime": "2019-08-09T19:00:00Z" 103 | } 104 | ] 105 | } 106 | ``` 107 | 108 | In the response, we can see that `moderationSettings` is not being returned. Additionally, the API producer is returning a `tips` instance annotation, informing the caller that this response only returns default properties, how to get the non-default properties, and where to find information about this type's properties. The `tips` instance annotation is only emitted if the `Prefer: ms-graph-dev-mode` HTTP request header is present. 109 | 110 | ### Calling an API with default properties and $select 111 | 112 | In this example, the caller needs `moderationSettings` for their API scenario. They try this out in Graph Explorer first. 113 | 114 | #### Request 115 | 116 | ```http 117 | GET /teams/{id}/channels?$select=id,membershipType,moderationSettings 118 | Prefer: ms-graph-dev-mode 119 | ``` 120 | 121 | #### Response 122 | 123 | ```http 124 | 200 ok 125 | Content-type: application/json 126 | ``` 127 | 128 | ```json 129 | { 130 | "@odata.context": "https://graph.microsoft.com/beta/$metadata#Collection(microsoft.graph.channel)", 131 | "value": [ 132 | { 133 | "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk1@thread.tacv2", 134 | "membershipType": "shared", 135 | "channelModerationSettings": { 136 | "userNewMessageRestriction": "everyone", 137 | "replyRestriction": "everyone", 138 | "allowNewMessageFromBots": true, 139 | "allowNewMessageFromConnectors": true 140 | } 141 | }, 142 | { 143 | "id": "19:PZC_kAPAm12RPBMkEaJyXaY_d2PE6mJV6MzO1EiCbnk2@thread.tacv2", 144 | "membershipType": "private", 145 | "channelModerationSettings": { 146 | "userNewMessageRestriction": "moderators", 147 | "replyRestriction": "authorAndModerators", 148 | "allowNewMessageFromBots": true, 149 | "allowNewMessageFromConnectors": true 150 | } 151 | } 152 | ] 153 | } 154 | ``` 155 | 156 | In this case, because the request has a `$select`, the `tips` instance annotation is not emitted. 157 | 158 | ### Calling an API without using $select 159 | 160 | The caller makes a `GET` request without $select, to an API that doesn't have any default properties, via Graph Explorer. 161 | 162 | #### Request 163 | 164 | ```http 165 | GET /me/todo/lists 166 | Prefer: ms-graph-dev-mode 167 | ``` 168 | 169 | #### Response 170 | 171 | ```http 172 | 200 ok 173 | Content-type: application/json 174 | ``` 175 | 176 | ```json 177 | { 178 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('99a6e897-8c54-4354-a739-626fbe28ed78')/todo/lists", 179 | "@microsoft.graph.tips": "Use $select to choose only the properties your app needs, as this can lead to performance improvements. For example: GET me/todo/lists?$select=displayName,isOwner", 180 | "value": [ 181 | { 182 | "@odata.etag": "W/\"c5yMNreru0OMO71/IwuKGQAG6WUnjQ==\"", 183 | "displayName": "Tasks", 184 | "isOwner": true, 185 | "isShared": false, 186 | "wellknownListName": "defaultList", 187 | "id": "AAMkADU3NTBhNWUzLWE0MWItNGViYy1hMTA0LTkzNjRlYTA2ZWI2ZAAuAAAAAAAFup0i-hqtR5N14AJlh2qTAQATqGUvrHrTEbWPAKDJQ2mMAAACWIG1AAA=" 188 | }, 189 | { 190 | "@odata.etag": "W/\"c5yMNreru0OMO71/IwuKGQAG6WUnmQ==\"", 191 | "displayName": "Outlook Commitments", 192 | "isOwner": true, 193 | "isShared": false, 194 | "wellknownListName": "none", 195 | "id": "AQMkADU3NTBhNWUzLWE0MWItNGViYy1hMTA0LTkzNjRlYTA2ZWI2ZAAuAAADBbqdIv4arUeTdeACZYdqkwEAc5yMNreru0OMO71-IwuKGQABWbOTpQAAAA==" 196 | } 197 | ] 198 | } 199 | ``` 200 | 201 | Notice how for this scenario, where there are no default properties and the caller does not use `$select`, there's a `tips` instance annotation, encouraging the app developer to use `$select`. This `tips` annotation is automatically added to the response by the API gateway service, as long as the workload service doesn't use response passthrough (in which case it is the responsibility of the workload service). 202 | -------------------------------------------------------------------------------- /graph/patterns/dictionary-client-guidance.md: -------------------------------------------------------------------------------- 1 | # Dictionary types 2 | 3 | > **Note:** This document will be moved into a central client guidance document in the future. 4 | 5 | *The client guidance is a collection of additional information provided to SDK implementers and client applications. This information is meant to help understand how various guidelines and concepts translate in their world and clarify a few unknowns. Always read the corresponding guideline first to get a contextual understanding.* 6 | 7 | For more information, see the [Dictionary](./dictionary.md) pattern. 8 | 9 | ## OpenAPI example 10 | 11 | The following json-schema/OpenAPI example defines a dictionary of which values are of type **RoleSettings**. 12 | 13 | In **components** in **schemas**: 14 | 15 | ```json 16 | { 17 | "roleSettings": { 18 | "type": "object", 19 | "properties": { 20 | "domain": { 21 | "type": "string" 22 | } 23 | } 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | ```json 30 | { 31 | "type": "object", 32 | "patternProperties": { 33 | ".*": { 34 | "$ref": "#/components/schemas/roleSettings" 35 | }, 36 | "additionalProperties": false 37 | } 38 | } 39 | ``` 40 | 41 | ## SDK support 42 | 43 | SDKs need to provide support for dictionary types so that SDK consumers get a delightful development experience. Examples are provided for different languages. Other aspects need to be taken into consideration: 44 | 45 | - Dictionaries support OData annotations (values prefixed with **@OData**); such annotations should not be inserted directly in the dictionary but rather in the additional properties manager. 46 | - Dictionary types can inherit another dictionary type; this inheritance must be respected. 47 | - Dictionary values can be of union types; if the target language doesn't support union types, a wrapper type should be generated as a backward compatible solution with properties for each type of the union. 48 | 49 | ### Dotnet 50 | 51 | ```CSharp 52 | Dictionary 53 | ``` 54 | 55 | ### Java 56 | 57 | ```Java 58 | Map 59 | ``` 60 | 61 | ### JavaScript/TypeScript 62 | 63 | ```TypeScript 64 | Map 65 | ``` 66 | 67 | or 68 | 69 | ```JavaScript 70 | { 71 | [key: string]: {value: RoleSettings} 72 | } 73 | ``` 74 | 75 | ## Request builder generation annotation 76 | 77 | By default, SDKs are not required to contain a set of request builders to run CRUD requests on entries in the dictionary. The dictionary is updated as a whole by consumers by sending requests to the parent entity. 78 | 79 | If a **SupportedHttpMethod** annotation is specified for the dictionary type, request builders should be generated to allow consumers to automatically update the entries. 80 | -------------------------------------------------------------------------------- /graph/patterns/dictionary.md: -------------------------------------------------------------------------------- 1 | # Dictionary 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | _The dictionary type provides the ability to create a set key/value pairs where the set of keys is dynamically specified by the API consumer._ 6 | 7 | ## Problem 8 | 9 | The API design requires a resource to include an unknown quantity of data values whose keys are defined by the API consumer. 10 | 11 | ## Solution 12 | 13 | API designers use a JSON object to represent a dictionary in an `application/json` response payload. When describing the model in CSDL, a new complex type can be created that derives from `graph.Dictionary` and optionally uses the `Org.OData.Validation.V1.OpenPropertyTypeConstraint` to constrain the type that can be used for the values in the dictionary as appropriate. 14 | 15 | Dictionary entries can be added, removed, or modified via `PATCH` to the dictionary property. Entries are removed by setting the property to `null`. 16 | 17 | ## When to use this pattern 18 | 19 | Before using a dictionary type in your API definition, make sure that your scenario fits the following criteria: 20 | 21 | - The data values MUST be related to one another semantically as a collection. 22 | - The values MUST be primitive or complex types. 23 | - The client MUST define the keys of this type, as opposed to the service defining them in advance. 24 | 25 | ### Alternatives 26 | 27 | - [Open extensions](https://docs.microsoft.com/graph/extensibility-open-users) when you want to provide clients the ability to extend Microsoft Graph. 28 | - [Complex types](https://docs.microsoft.com/odata/webapi/complextypewithnavigationproperty) when the set of data values are known. 29 | 30 | ## Issues and considerations 31 | 32 | Dictionaries, sometimes called maps, are a collection of name-value pairs. They allow dynamic data sets to be accessed in a systematic manner and are a good compromise between a strictly defined-ahead-of-time structure with all its named properties and a loosely defined dynamic object (such as OData OpenTypes). 33 | 34 | Because dictionary entries are removed by setting the value to `null`, dictionaries don't support null values. 35 | 36 | For more information, see the [OData reference](https://github.com/oasis-tcs/odata-vocabularies/blob/master/vocabularies/Org.OData.Core.V1.md#dictionary). 37 | 38 | ## Examples 39 | 40 | ### String dictionary 41 | 42 | #### CSDL declaration 43 | The following example demonstrates defining a dictionary that can contain string values. 44 | 45 | ```xml 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Edm.String 56 | 57 | 58 | 59 | 60 | ``` 61 | 62 | Please note that schema validation will fail due to the casing of `Dictionary`. 63 | This warning should be suppressed. 64 | 65 | #### Defining a dictionary property 66 | The following example shows defining a dictionary property, "userTags", on the item entity type. 67 | 68 | ```xml 69 | 70 | ... 71 | 72 | 73 | ``` 74 | 75 | #### Reading a dictionary 76 | Dictionaries are represented in JSON payloads as a JSON object, where the property names are comprised of the keys and their values are the corresponding key values. 77 | 78 | The following example shows reading an item with a dictionary property named "userTags": 79 | 80 | ```HTTP 81 | GET /item 82 | ``` 83 | Response: 84 | ```json 85 | { 86 | ... 87 | "userTags": 88 | { 89 | "anniversary": "2002-05-19", 90 | "favoriteMovie": "Princess Bride" 91 | } 92 | } 93 | ``` 94 | 95 | #### Setting a dictionary value 96 | The following example shows setting a dictionary value. If "hairColor" already exists, it is updated, otherwise it is added. 97 | 98 | ```http 99 | PATCH /item/userTags 100 | ``` 101 | ```json 102 | { 103 | "hairColor": "purple" 104 | } 105 | ``` 106 | 107 | #### Deleting a dictionary value 108 | A dictionary value can be removed by setting the value to null. 109 | ```http 110 | PATCH /item/userTags 111 | ``` 112 | ```json 113 | { 114 | "hairColor": null 115 | } 116 | ``` 117 | 118 | ### Complex typed dictionary 119 | 120 | #### CSDL declaration 121 | Dictionaries can also contain complex types whose values may be constrained to a particular set of complex types. 122 | 123 | The following example defines a complex type **roleSettings**, an **assignedRoleGroupDictionary** that contains **roleSettings**, and an **assignedRoles** property that uses the dictionary.. 124 | 125 | ```xml 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ... 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | of roleSettings 144 | keyed by name of roleGroup. --> 145 | 146 | 147 | microsoft.graph.roleSettings 148 | 149 | 150 | 151 | 152 | ``` 153 | 154 | #### Reading a entity with a complex-typed dictionary 155 | 156 | The following example illustrates reading an entity containing the complex-typed dictionary "assignedRoles". 157 | 158 | ```HTTP 159 | GET /users/10 160 | ``` 161 | 162 | Response: 163 | 164 | ```json 165 | { 166 | "id": "10", 167 | "displayName": "Jane Smith", 168 | "assignedRoles": { 169 | "author": { 170 | "domain": "contoso" 171 | }, 172 | "maintainer": { 173 | "domain": "fabrikam" 174 | }, 175 | "architect": { 176 | "domain": "adventureWorks" 177 | } 178 | } 179 | } 180 | ``` 181 | 182 | #### Reading the dictionary property 183 | The following example shows getting just the "assignedRoles" dictionary property. 184 | 185 | ```HTTP 186 | GET /users/10/assignedRoles 187 | ``` 188 | 189 | Response: 190 | 191 | ```json 192 | { 193 | "author": { 194 | "domain": "contoso" 195 | }, 196 | "maintainer": { 197 | "domain": "fabrikam" 198 | }, 199 | "architect": { 200 | "domain": "adventureWorks" 201 | } 202 | } 203 | ``` 204 | 205 | #### Reading an individual entry from the dictionary 206 | The following example shows reading a single complex-typed entry named "author" from the "assignedRoles" dictionary. 207 | 208 | ```HTTP 209 | GET /users/10/assingedRoles/author 210 | ``` 211 | 212 | Response: 213 | 214 | ```json 215 | { 216 | "domain": "contoso" 217 | } 218 | ``` 219 | 220 | #### Setting an individual entry in the dictionary 221 | The following examples shows updating the dictionary to set the value for the "author" entry. If the "author" entry does not exists it is added with the specified values; otherwise, if the "author" entry already exists, it is updated with the specified values (unspecified values are left unchanged). 222 | 223 | ```HTTP 224 | PATCH /users/10/assignedRoles/author 225 | ``` 226 | ```json 227 | { 228 | "author" : { 229 | "domain": "contoso" 230 | } 231 | } 232 | ``` 233 | 234 | #### Deleting an individual entry from the dictionary 235 | The following example shows deleting the "author" entry by setting its value to null. 236 | 237 | ```HTTP 238 | PATCH /users/10/assignedRoles 239 | ``` 240 | ```json 241 | { 242 | "author": null 243 | } 244 | ``` 245 | 246 | #### Setting multiple dictionary entries 247 | The following example sets values for the "author", "maintainer" and "viewer" entries, and removes the "architect" entry by setting it to null. 248 | 249 | ```HTTP 250 | PATCH /users/10/assignedRoles 251 | ``` 252 | ```json 253 | { 254 | "author": { 255 | "domain": "contoso1" 256 | }, 257 | "maintainer": { 258 | "domain": "fabrikam1" 259 | }, 260 | "reviewer": { 261 | "domain": "fabrikam" 262 | }, 263 | "architect": null 264 | } 265 | 266 | ``` 267 | 268 | ## See also 269 | 270 | - [SDK implementation guidance](./dictionary-client-guidance.md) 271 | -------------------------------------------------------------------------------- /graph/patterns/enums.md: -------------------------------------------------------------------------------- 1 | ### Enums 2 | 3 | In OData, enums represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options. 4 | 5 | ```xml 6 | 7 | 8 | 9 | 10 | 11 | ``` 12 | 13 | #### Pros 14 | 15 | - Our SDK generators will translate the enum to the best representation of the target programming language, resulting in a better developer experience and free client side validation 16 | 17 | #### Cons 18 | 19 | - Adding a new value requires to go through a (generally fast) API Review 20 | - If the enum is not [evolvable](./patterns/evolvable-enums.md), adding a new value is a breaking change and will generally not be allowed 21 | 22 | #### Enum or Booleans 23 | 24 | Enumerations are a good alternative to Booleans when one of the two values (`true`, `false`) conveys other possible values not yet conceived. Let's assume we have an `publicNotification` type and a property to communicate how to display it: 25 | 26 | ```xml 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | The `false` value here merely communicates that the notification shall not be displayed as a tip. What if, in the future, the notification could be displayed as a `tip` or `alert`, and then in a more distant future, a `dialog` option is viable? 35 | 36 | With the current model, the only way is to add more boolean properties to convey the new information: 37 | 38 | ```diff 39 | 40 | 41 | 42 | 43 | + 44 | + 45 | 46 | ``` 47 | 48 | Additionally speaking, the workload will now also have to validate the data structure and make sure that only one of the 3 values is `true` 49 | 50 | By using an evolvable enum, instead, all we need to do is to add new members: 51 | 52 | ```diff 53 | 54 | 55 | 56 | + 57 | - 58 | - 59 | - 60 | 61 | ``` 62 | 63 | ```xml 64 | 65 | 66 | 67 | 68 | 69 | 70 | ``` 71 | 72 | Similarly speaking, if you find yourself using a `nullable` Enum, that is a indication that maybe what you are trying to model is something that has 3 states and an enum is more appropraite. For instance, let's assume we have a boolean property called `syncEnabled`, where `null` means that the value is undefined and inherited from the general tenant configuration. Instead of modelling like a boolean: 73 | 74 | ```xml 75 | 76 | ``` 77 | 78 | An enum not only better conveys the message: 79 | 80 | ```xml 81 | 82 | 83 | 84 | 85 | 86 | 87 | ``` 88 | 89 | but it is also open for future scenarios: 90 | 91 | ```diff 92 | 93 | 94 | 95 | 96 | 97 | + 98 | 99 | ``` 100 | 101 | Additionally speaking, depending on the situation, a nullable enum can very likely be avoided by adding a `none` member. 102 | 103 | If used, `EnumType` names should be singular if the are non-flags enums, and the names should be plural if they are flags enums. 104 | 105 | #### Flag Enums or Collection of Enums 106 | 107 | In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums: 108 | 109 | ```xml 110 | 111 | ``` 112 | 113 | However, [Flagged Enums](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530378) can model this use case scenario: 114 | 115 | ```diff 116 | - 117 | + 118 | - 119 | + 120 | - 121 | + 122 | - 123 | + 124 | - 125 | + 126 | 127 | ``` 128 | 129 | With such enum, customers can select multiple values in a single field: 130 | 131 | `displayMethod = tip | alert` 132 | 133 | In cases where two properties want to use the same *conceptual* `EnumType`, but one property is a collection while the other is single-values, the model should define *two* separate `EnumType`s, one being a non-flags enum with a singular name and the other marked as a flags enum with its name being the plural form of the non-flags enum. 134 | 135 | #### Flag enum + non-flag enum 136 | 137 | There are occasions where one API will want to use a non-flag enum, but another API will want a flags enum. 138 | For example, the `displayMethod` example above may have one API that is configuring which display methods to use, and another API which is configuring that particular display method. 139 | In this case, the first API will want a flags enum, but the second API will want to only allow configuring one display method at a time, and will therefore prefer a non-flags enum. 140 | 141 | Two enum types should be defined, one as a flags enum and the other as a non-flags enum. 142 | The flags enum should be named such that it is plural, and the non-flags enum should be named such that it is singular. 143 | The two types should be kept in sync with each other. 144 | -------------------------------------------------------------------------------- /graph/patterns/evolvable-enums.md: -------------------------------------------------------------------------------- 1 | # Evolvable enums 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The evolvable enums pattern allows API producers to extend enumerated types with new members without breaking API consumers.* 6 | 7 | Note: You might be interested in reading the [Enum guidance](./enums.md) first 8 | 9 | ## Problem 10 | 11 | Frequently API producers want to add new members to an enum type after it is initially published. Some serialization libraries might fail when they encounter members in an enum type that were added after the serialization model was generated. In this documentation, we refer to any added enum members as unknown. 12 | 13 | ## Solution 14 | 15 | The solution is to add a 'sentinel' member named `unknownFutureValue` at the end of the currently known enum members. The API producer then replaces any member that is numerically after `unknownFutureValue` with `unknownFutureValue`. 16 | 17 | If an API consumer can handle unknown enum values, the consumer can opt into receiving the unknown enum members by specifying the `Prefer: include-unknown-enum-members` HTTP header in their requests. The API producer then indicates that this preference has been applied by returning the `Preference-Applied: include-unknown-enum-members` HTTP header in the response. 18 | 19 | ## When to use this pattern 20 | 21 | It is a best practice to include an `unknownFutureValue` value when the enum is initially introduced to allow flexibility to extend the enum during the lifetime of the API. Even if the API producer believes that they have included all possible members in an enum, we still strongly recommend that you include an `unknownFutureValue` member to allow for unforeseen future circumstances that may require extending the enum. 22 | 23 | This pattern must not be used in scenarios where an API consumer wants to use enum members that are not known to the API producer. 24 | 25 | ## Issues and considerations 26 | 27 | Consider the following: 28 | 29 | - An enum member with the name of `unknownFutureValue` MUST only be used as a sentinel value. An API producer MUST not include a member named `unknownFutureValue` in an enum for any other purpose. 30 | 31 | - Changing the value (that is, position) of the `unknownFutureValue` sentinel member is considered a breaking change and must follow the [deprecation](../deprecation.md) process. 32 | 33 | - Enum types can have multiple members with the same numeric value to allow for aliasing enum members. `unknownFutureValue` MUST not be aliased to any other enum member. 34 | 35 | - There is no ability for a client to indicate that it can handle a subset of unknown enum members. Instead, they can only specify that either they cannot handle any unknown enum members or they can handle any unknown enum members. 36 | 37 | - The `Prefer: include-unknown-enum-members` header applies to all included enums in the request/response. There is no way for an API consumer to apply the behavior to only a subset of enum types. 38 | 39 | - New values MUST not be inserted into the enum before `unknownFutureValue`. Implementers are recommended to make the numeric value of `unknownFutureValue` one greater than the last known enum member to ensure that there are no gaps into which a new member could be inadvertently added. The exception to this is the case of flagged enums, in which case the value of `unknownFutureValue` should be the next power of 2 value. 40 | 41 | - For flagged enums, care should be exercised to ensure that `unknownFutureValue` is not included in any enum members that represent a combination of other enum members. 42 | 43 | - If the value of a property containing a flag enum contains multiple unknown values, they should all be replaced with a single `unknownFutureValue` value (that is, there should not be multiple `unknownFutureValue` values returned). 44 | 45 | - If an API consumer specifies `unknownFutureValue` for the value of a property in a `POST`/`PUT` request or as a parameter of an action or function, the API producer must reject the request with a `400 Bad Request` HTTP status. 46 | 47 | - If an API consumer specifies `unknownFutureValue` for the value of a property in a `PATCH` request, the API producer must treat the property as if it were absent (that is, the existing value should not be changed). In the case where the API producer treats `PATCH` as an upsert, the call MUST be rejected with a `400 Bad Request` HTTP status. 48 | 49 | - If an API consumer specifies an enum member greater than `unknownFutureValue` in any request without specifying the `Prefer: include-unknown-enum-members` header, the API producer must reject the request with a `400 Bad Request` HTTP status. 50 | 51 | - For details about how the `unknownFutureValue` value is handled as part of a `$filter` clause, consult the following examples: 52 | 53 | - **CSDL** 54 | 55 | ```xml 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | - **Filter behavior** 69 | 70 | | `$filter` clause | `Prefer: include-unknown-enum-members` Absent | `Prefer: include-unknown-enum-members` Present | 71 | |---|---|---| 72 | | `enumProperty eq unknownFutureValue`| Return entities where enumProperty has any value greater than `unknownFutureValue` replacing actual value with `unknownFutureValue`| Return nothing | 73 | | `enumProperty gt unknownFutureValue`| Return entities where enumProperty has any value greater than `unknownFutureValue` replacing actual value with `unknownFutureValue` | Return entities where enumProperty has any value greater than `unknownFutureValue` | 74 | | `enumProperty lt unknownFutureValue`| Return entities where enumProperty has any known value (i.e. less than `unknownFutureValue`) | Return entities where enumProperty has any value less than `unknownFutureValue`| 75 | | `enumProperty eq newValue` | `400 Bad Request` | Return entities where enumProperty has the value `newValue` | 76 | | `enumProperty gt newValue` | `400 Bad Request` | Return entities where enumProperty has a value greater than `newValue` | 77 | | `enumProperty lt newValue` | `400 Bad Request` | Return entities where enumProperty has a value less than `newValue` | 78 | 79 | - If an evolvable enum is included in an `$orderby` clause, the actual numeric value of the member should be used to order the collection. After sorting, the member should then be replaced with `unknownFutureValue` when the `Prefer: include-unknown-enum-members` header is absent. 80 | 81 | ## Examples 82 | 83 | For the following examples, we consider the `managedDevice` entity, which refers to the `managedDeviceArchitecture` enum type. 84 | 85 | ```xml 86 | 87 | 88 | 89 | 90 | 91 | ``` 92 | 93 | When the `managedDeviceArchitecture` enum was initially published to Microsoft Graph, it was defined as follows: 94 | 95 | ```xml 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | ``` 106 | 107 | The enum was later extended to add a new value of `quantum`, leading to the following CSDL: 108 | 109 | ```xml 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | ``` 120 | 121 | ### Default behavior 122 | 123 | ```http 124 | GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture 125 | ``` 126 | 127 | ```json 128 | { 129 | "value": [ 130 | { 131 | "id": "0", 132 | "displayName": "Surface Pro X", 133 | "processorArchitecture" : "arm64" 134 | }, 135 | { 136 | "id": "1", 137 | "displayName": "Prototype", 138 | "processorArchitecture": "unknownFutureValue" 139 | } 140 | { 141 | "id": "2", 142 | "displayName": "My Laptop", 143 | "processorArchitecture": "x64" 144 | } 145 | ] 146 | } 147 | ``` 148 | 149 | In this case, the value of the `processorArchitecture` property is `quantum`. However, because the client did not request the `include-unknown-enum-members` header, the value was replaced with `unknownFutureValue`. 150 | 151 | ### Include opt-in header 152 | 153 | ```http 154 | GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture 155 | 156 | Prefer: include-unknown-enum-members 157 | ``` 158 | 159 | ```json 160 | Preference-Applied: include-unknown-enum-members 161 | 162 | { 163 | "value": [ 164 | { 165 | "displayName": "Surface Pro X", 166 | "processorArchitecture" : "arm64" 167 | }, 168 | { 169 | "displayName": "Prototype", 170 | "processorArchitecture": "quantum" 171 | }, 172 | { 173 | "displayName": "My Laptop", 174 | "processorArchitecture": "x64" 175 | } 176 | ] 177 | } 178 | ``` 179 | 180 | ### Default sort behavior 181 | 182 | ```http 183 | GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$orderBy=processorArchitecture 184 | ``` 185 | 186 | ```json 187 | { 188 | "value": [ 189 | { 190 | "displayName": "Surface Pro X", 191 | "processorArchitecture" : "arm64" 192 | }, 193 | { 194 | "displayName": "My Laptop", 195 | "processorArchitecture": "x64" 196 | }, 197 | { 198 | "displayName": "Prototype", 199 | "processorArchitecture": "unknownFutureValue" 200 | } 201 | ] 202 | } 203 | ``` 204 | 205 | ### Sort behavior with opt-in header 206 | 207 | ```http 208 | GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture 209 | 210 | Prefer: include-unknown-enum-members 211 | ``` 212 | 213 | ```json 214 | Preference-Applied: include-unknown-enum-members 215 | 216 | { 217 | "value": [ 218 | { 219 | "displayName": "Surface Pro X", 220 | "processorArchitecture" : "arm64" 221 | }, 222 | { 223 | "displayName": "My Laptop", 224 | "processorArchitecture": "x64" 225 | }, 226 | { 227 | "displayName": "Prototype", 228 | "processorArchitecture": "quantum" 229 | } 230 | ] 231 | } 232 | ``` 233 | 234 | ### Default filter behavior 235 | 236 | ```http 237 | GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 238 | ``` 239 | 240 | ```json 241 | { 242 | "value": [ 243 | { 244 | "displayName": "My Laptop", 245 | "processorArchitecture": "x64" 246 | }, 247 | { 248 | "displayName": "Prototype", 249 | "processorArchitecture": "unknownFutureValue" 250 | } 251 | ] 252 | } 253 | ``` 254 | 255 | ### Filter behavior with opt-in header 256 | 257 | ```http 258 | GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64 259 | 260 | Prefer: include-unknown-enum-members 261 | ``` 262 | 263 | ```json 264 | Preference-Applied: include-unknown-enum-members 265 | 266 | { 267 | "value": [ 268 | { 269 | "displayName": "My Laptop", 270 | "processorArchitecture": "x64" 271 | }, 272 | { 273 | "displayName": "Prototype", 274 | "processorArchitecture": "quantum" 275 | } 276 | ] 277 | } 278 | ``` 279 | 280 | ### Patch example 281 | 282 | ```http 283 | PATCH https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 284 | 285 | { 286 | "displayName": "Secret Prototype", 287 | "processorArchitecture": "unknownFutureValue" 288 | } 289 | ``` 290 | 291 | ```json 292 | { 293 | "id": "1", 294 | "displayName": "Secret Prototype", 295 | "processorArchitecture": "unknownFutureValue" 296 | } 297 | ``` 298 | 299 | ```http 300 | GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1 301 | Prefer: include-unknown-enum-members 302 | ``` 303 | 304 | ```json 305 | Preference-Applied: include-unknown-enum-members 306 | 307 | { 308 | "id": "1", 309 | "displayName": "Secret Prototype", 310 | "processorArchitecture": "quantum" 311 | } 312 | ``` 313 | 314 | ## Flag enum examples 315 | 316 | For the following examples, we consider the `windowsUniversalAppX` entity, which refers to the `windowsArchitecture` flag enum type. 317 | 318 | ```xml 319 | 320 | 321 | 322 | 323 | 324 | ``` 325 | 326 | When the `windowsArchitecture` enum was initially published to Microsoft Graph, it was defined as follows: 327 | 328 | ```xml 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | ``` 339 | 340 | The enum was later extended to add a new value of `quantum`, leading to the following CSDL: 341 | 342 | ```xml 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | ``` 353 | 354 | ### Flag enum default behavior 355 | 356 | ```http 357 | GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures 358 | ``` 359 | 360 | ```json 361 | { 362 | "value": [ 363 | { 364 | "id": "0", 365 | "displayName": "OneNote", 366 | "applicableArchitectures" : "neutral" 367 | }, 368 | { 369 | "id": "1", 370 | "displayName": "Minecraft", 371 | "applicableArchitectures": "x86,x64,arm,unknownFutureValue" 372 | } 373 | { 374 | "id": "2", 375 | "displayName": "Edge", 376 | "applicableArchitectures": "x64,arm,unknownFutureValue" 377 | } 378 | ] 379 | } 380 | ``` 381 | 382 | In this case, the value of the `applicableArchitectures` property includes `quantum`. However, because the client did not request the `include-unknown-enum-members` header, the value was replaced with `unknownFutureValue`. 383 | 384 | ### Flag enum include opt-in header 385 | 386 | ```http 387 | GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures 388 | 389 | Prefer: include-unknown-enum-members 390 | ``` 391 | 392 | ```json 393 | Preference-Applied: include-unknown-enum-members 394 | 395 | { 396 | "value": [ 397 | { 398 | "id": "0", 399 | "displayName": "OneNote", 400 | "applicableArchitectures" : "neutral" 401 | }, 402 | { 403 | "id": "1", 404 | "displayName": "Minecraft", 405 | "applicableArchitectures": "x86,x64,arm,quantum" 406 | } 407 | { 408 | "id": "2", 409 | "displayName": "Edge", 410 | "applicableArchitectures": "x64,arm,quantum" 411 | } 412 | ] 413 | } 414 | ``` 415 | 416 | ### Flag enum default filter behavior 417 | 418 | ```http 419 | GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue 420 | ``` 421 | 422 | ```json 423 | { 424 | "value": [ 425 | { 426 | "id": "1", 427 | "displayName": "Minecraft", 428 | "applicableArchitectures": "x86,x64,arm,unknownFutureValue" 429 | } 430 | { 431 | "id": "2", 432 | "displayName": "Edge", 433 | "applicableArchitectures": "x64,arm,unknownFutureValue" 434 | } 435 | ] 436 | } 437 | ``` 438 | 439 | ### Flag enum include opt-in header filter behavior 440 | 441 | ```http 442 | GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps?$select=displayName,applicableArchitectures&$filter=applicableArchitectures has unknownFutureValue 443 | 444 | Prefer: include-unknown-enum-members 445 | ``` 446 | 447 | ```json 448 | Preference-Applied: include-unknown-enum-members 449 | 450 | { 451 | "value": [] 452 | } 453 | ``` 454 | 455 | ### Flag enum patch example 456 | 457 | ```http 458 | PATCH https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps/1 459 | 460 | { 461 | "displayName": "Minecraft 2", 462 | "processorArchitecture": "unknownFutureValue" 463 | } 464 | ``` 465 | 466 | ```json 467 | { 468 | "id": "1", 469 | "displayName": "Minecraft 2", 470 | "applicableArchitectures": "unknownFutureValue" 471 | } 472 | ``` 473 | 474 | ```http 475 | GET https://graph.microsoft.com/v1.0/deviceAppManagement/mobileApps/1 476 | 477 | Prefer: include-unknown-enum-members 478 | ``` 479 | 480 | ```json 481 | Preference-Applied: include-unknown-enum-members 482 | 483 | { 484 | "id": "1", 485 | "displayName": "Minecraft 2", 486 | "applicableArchitectures": "x86,x64,arm,quantum" 487 | } 488 | ``` 489 | -------------------------------------------------------------------------------- /graph/patterns/facets.md: -------------------------------------------------------------------------------- 1 | # Facets 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *A frequent pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with common properties and facets for variants.* 6 | 7 | ## Problem 8 | 9 | An API designer needs to model a set of heterogeneous resources that have common properties and behaviors and might express features of multiple variants at a time because variants are not mutually exclusive. 10 | For example, a movie clip stored on OneDrive is both a file and a video. There are properties associated to each variant. 11 | 12 | ## Solution 13 | 14 | API designers create multiple complex types to bundle properties for each variant, and then define an entity type with a property for each complex type to hold the properties of the variant. 15 | 16 | In this solution, a child variant is identified by the presence of one or more facets in the parent object. 17 | 18 | ## When to use this pattern 19 | 20 | The facets pattern is useful when there is a number of variants and they are not mutually exclusive. It also makes it syntactically easier to query resources by using the OData `$filter` expression because it doesn't require casting. 21 | 22 | You can consider related patterns such as [type hierarchy](./subtypes.md) and [flat bag of properties](./flat-bag.md). 23 | 24 | ## Issues and considerations 25 | 26 | When introducing a new facet, you need to ensure that the new facet doesn't change the semantic of the model with its implicit constraints. 27 | 28 | ## Example 29 | 30 | The driveItem resource represents a file, folder, image, or other item stored in a drive and is modeled by using an entity type with multiple facets. 31 | 32 | ```XML 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ... 62 | 63 | ``` 64 | 65 | An API request to get all items from a personal OneDrive returns a heterogenous collection with different facets populated. In the following example, there is a folder, a file, and an image in the collection. The image entity has two facets populated: file and image. 66 | 67 | ``` 68 | GET https://graph.microsoft.com/v1.0/me/drive/root/children 69 | 70 | Response shortened for readability: 71 | 72 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('93816c1c-1b19-41de-a322-a1643d7f4d39')/drive/root/children", 73 | "value": [ 74 | { 75 | "createdDateTime": "2021-07-07T13:59:47Z", 76 | "name": "Microsoft Teams Chat Files", 77 | ..., 78 | "folder": { 79 | "childCount": 15 80 | } 81 | }, 82 | ... 83 | { 84 | "createdDateTime": "2021-12-15T00:07:36Z", 85 | "name": "Versioning and Deprecation.docx", 86 | ..., 87 | "file": { 88 | "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 89 | "hashes": { 90 | "quickXorHash": "r2d9uZilW0zEIXwycymsUQzhV+U=" 91 | } 92 | }, 93 | ... 94 | }, 95 | { 96 | "createdDateTime": "2021-12-21T16:32:51Z", 97 | "name": "WhaleShark.jpg", 98 | ... 99 | "file": { 100 | "mimeType": "image/jpeg", 101 | "hashes": { 102 | "quickXorHash": "2vHpAA7RDZJteIwl1pXR980xuh4=" 103 | } 104 | }, 105 | ..., 106 | "image": {} 107 | } 108 | ] 109 | ``` 110 | -------------------------------------------------------------------------------- /graph/patterns/flat-bag.md: -------------------------------------------------------------------------------- 1 | # Flat bag of properties 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *A known pattern in Microsoft Graph is to model multiple variants of a common concept as a single entity type with all potential properties plus an additional property to distinguish the variants.* 6 | 7 | ## Problem 8 | 9 | API designers need to model a small and limited number of variants of a common concept with a concise list of non-overlapping properties and consistent behavior across variants. The designer also wants to simplify query construction. 10 | 11 | ## Solution 12 | 13 | The API designer creates one entity type with all the potential properties plus an additional property to distinguish the variants, often called `variantType`. For each value of `variantType`, some properties are meaningful and others are ignored. 14 | 15 | ## When to use this pattern 16 | 17 | The flat bag pattern is useful when there is a small number of variants with similar behavior, and variants are queried for mostly read-only operations. The pattern also makes it syntactically easier to query resources by using the OData `$filter` expression because it doesn't require casting. 18 | 19 | ## Issues and considerations 20 | 21 | In general, the flat bag pattern is the least recommended modeling choice because it is weakly typed, and it is difficult to semantically verify targeted resource modifications. However, there are circumstances when query simplicity and a limited number of properties might overweight considerations of a more strongly typed approach. 22 | The pattern is not recommended for a large number of variants and properties because the payload becomes sparsely populated. 23 | 24 | You can consider related patterns such as [type hierarchy](./subtypes.md) and [facets](./facets.md). 25 | 26 | ## Example 27 | 28 | A good example for flat bag implementation is the recurrencePattern type on [recurrencePattern](https://docs.microsoft.com/graph/api/resources/recurrencepattern). 29 | 30 | The recurrencePattern has six variants expressed as six different values of the `type` property (for example: daily, weekly, ...). The key here is that for each of these values, some properties are meaningful and others are ignored (for example: `daysOfWeek` is relevant when `type` is `weekly` but not when it is `daily`). 31 | 32 | ``` 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ``` -------------------------------------------------------------------------------- /graph/patterns/long-running-operations.md: -------------------------------------------------------------------------------- 1 | # Long running operations 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The long running operations (LRO) pattern provides the ability to model operations where processing a client request takes a long time, but the client isn't blocked and can do some other work until operation completion.* 6 | 7 | ## Problem 8 | 9 | The API design requires modeling operations on resources, which takes a long time 10 | to complete, so that API clients don't need to wait and can continue doing other 11 | work while waiting for the final operation results. The client should be able to 12 | monitor the progress of the operation and have an ability to cancel it if 13 | needed. 14 | 15 | The API needs to provide a mechanism to track the work 16 | being done in the background. The mechanism needs to be expressed in the same 17 | web style as other interactive APIs. It also needs to support checking on the status and/or 18 | being notified asynchronously of the results. 19 | 20 | ## Solution 21 | 22 | The solution is to model the API as a synchronous service that returns a 23 | resource that represents the eventual completion or failure of a long running 24 | operation. 25 | 26 | There are two flavors of this solution: 27 | 28 | - The returned resource is the targeted resource and includes the status of 29 | the operation. This pattern is often called RELO (resource-based long running operation). 30 | 31 | 32 |

33 | The status monitor LRO flow 34 |

35 | 36 | 37 | - The returned resource is a new API resource called *stepwise operation* and is created to track the status. This LRO solution is similar to the concept of Promises or Futures in other programming languages. 38 | 39 | 40 |

41 | The status monitor LRO flow 42 |

43 | 44 | 45 | The RELO pattern is the preferred pattern for long running operations and should be 46 | used wherever possible. The pattern avoids complexity, and consistent resource 47 | presentation makes things simpler for our users and tooling chain. 48 | 49 | - For the RELO pattern, you should return the Location header that indicates the location of the resource. 50 | - The API response says the targeted resource is being created by returning a 201 status code and the resource URI is provided in the Location header, but the response indicates that the request is not completed by including "Provisioning" status. 51 | 52 | - For the LRO pattern, you should return the Location header that indicates the location of a new stepwise operation resource. 53 | - The API response says the operation resource is being created at the URL provided in the Location header and indicates that the request is not completed by including a 202 status code. 54 | - Microsoft Graph doesn’t allow tenant-wide operation resources; therefore, stepwise operations are often modeled as a navigation property on the target resource. 55 | 56 | - For most implementations of the LRO pattern (like the example above), there will be 3 permissions necessary to comply with the principle of least privilege: `ArchiveOperation.ReadWrite.All` to create the `archiveOperation` entity, `ArchiveOperation.Read.All` to track the `archiveOperation` entity to completion, and `Archives.Read.All` to retrieve the `archive` that was created as a result of the operation. 57 | For APIs that would have been modeled as a simple `GET` on the resource URL, but that are modeled as long-running operations due to MSGraph performance requirements, only the `Archive.Read.All` permission is necessary as long as creating the `archiveOperation` entity is "safe". 58 | Here, "safe" means that there are no side effects of creating the `archiveOperation` entity that would change the functionality of any entities outside of the `archive` being retrieved. 59 | This requirment does not mean that the API must be idempotent, but an idempotent API is suffucient to meet this requirement. 60 | 61 | ## When to use this pattern 62 | 63 | Any API call that is expected to take longer than one second in the 99th percentile should use the long running operations pattern. 64 | 65 | How do you select which flavor of LRO pattern to use? An API designer can follow these heuristics: 66 | 67 | 1. If a service can create a resource with a minimal latency and continue updating its status according to the well-defined and stable state transition model until completion, then the RELO model is the best choice. 68 | 69 | 2. Otherwise, a service should follow the stepwise operation pattern. 70 | 71 | ## Issues and considerations 72 | 73 | - One or more API consumers MUST be able to monitor and operate on the same resource at the same time. 74 | 75 | - The state of the system SHOULD always be discoverable and testable. Clients 76 | SHOULD be able to determine the system state even if the operation tracking 77 | resource is no longer active. Clients MAY issue a GET on some resource to 78 | determine the state of a long running operation. 79 | 80 | - The long running operations pattern SHOULD work for clients looking to "fire and forget" 81 | and for clients looking to actively monitor and act upon results. 82 | 83 | - The long running operations pattern might be supplemented by the [change notification pattern](./change-notification.md). 84 | 85 | - Cancellation of a long running operation does not explicitly mean a rollback. On a per API-defined case, it 86 | might mean a rollback or compensation or completion or partial completion, 87 | etc. Following a canceled operation, the API should return a consistent state that allows 88 | continued service. 89 | 90 | - A recommended minimum retention time for a stepwise operation is 24 hours. 91 | Operations SHOULD transition to "tombstone" for an additional period of time 92 | prior to being purged from the system. 93 | 94 | - Services that provide a new operation resource MUST support GET semantics on the operation. 95 | - Services that return a new operation MUST always return an LRO (even if the LRO is created in the completed state); that way API consumers don't have to deal with two different shapes of response. 96 | 97 | ## Examples 98 | 99 | ### Create a new resource using RELO 100 | 101 | A client wants to provision a new database: 102 | 103 | ``` 104 | POST https://graph.microsoft.com/v1.0/storage/databases/ 105 | 106 | { 107 | "displayName": "Retail DB", 108 | } 109 | ``` 110 | 111 | The API responds synchronously that the database has been created and indicates 112 | that the provisioning operation is not fully completed by including the 113 | Content-Location header and status property in the response payload: 114 | 115 | ``` 116 | HTTP/1.1 201 Created 117 | Location: https://graph.microsoft.com/v1.0/storage/databases/db1 118 | 119 | { 120 | "id": "db1", 121 | "displayName": "Retail DB", 122 | "status": "provisioning", 123 | [ … other fields for "database" …] 124 | } 125 | ``` 126 | 127 | The client waits for a period of time, and then invokes another request to try to get the database status: 128 | 129 | ``` 130 | GET https://graph.microsoft.com/v1.0/storage/databases/db1 131 | 132 | HTTP/1.1 200 Ok 133 | { 134 | "id": "db1", 135 | "displayName": "Retail DB", 136 | "status": "succeeded", 137 | [ … other fields for "database" …] 138 | } 139 | ``` 140 | 141 | ### Cancel RELO operation 142 | 143 | A client wants to cancel provisioning of a new database: 144 | 145 | ``` 146 | DELETE https://graph.microsoft.com/v1.0/storage/databases/db1 147 | 148 | ``` 149 | 150 | The API responds synchronously that the database is being deleted and indicates 151 | that the operation is accepted and is not fully completed by including the 152 | status property in the response payload. The API might provide a 153 | recommendation to wait for 30 seconds: 154 | 155 | ``` 156 | HTTP/1.1 202 Accepted 157 | Retry-After: 30 158 | 159 | { 160 | "id": "db1", 161 | "displayName": "Retail DB", 162 | "status": "deleting", 163 | [ … other fields for "database" …] 164 | } 165 | ``` 166 | 167 | The client waits for a period of time, and then invokes another request to try to get the deletion status: 168 | 169 | ``` 170 | GET https://graph.microsoft.com/v1.0/storage/databases/db1 171 | 172 | HTTP/1.1 404 Not Found 173 | ``` 174 | ### Create a new resource using the stepwise operation 175 | 176 | ``` 177 | POST https://graph.microsoft.com/v1.0/storage/archives/ 178 | 179 | { 180 | "displayName": "Image Archive", 181 | ... 182 | } 183 | ``` 184 | 185 | The API responds synchronously that the request has been accepted and includes 186 | the Location header with an operation resource for further polling: 187 | 188 | ``` 189 | HTTP/1.1 202 Accepted 190 | 191 | Location: https://graph.microsoft.com/v1.0/storage/operations/123 192 | 193 | ``` 194 | 195 | ### Poll on a stepwise operation 196 | 197 | ``` 198 | 199 | GET https://graph.microsoft.com/v1.0/storage/operations/123 200 | ``` 201 | 202 | The server responds that results are still not ready and optionally provides a 203 | recommendation to wait 30 seconds: 204 | 205 | ``` 206 | HTTP/1.1 200 OK 207 | Retry-After: 30 208 | 209 | { 210 | "createdDateTime": "2015-06-19T12-01-03.4Z", 211 | "lastActionDateTime": "2015-06-19T12-01-03.45Z", 212 | "status": "running" 213 | } 214 | ``` 215 | 216 | The client waits the recommended 30 seconds and then invokes another request to get 217 | the results of the operation: 218 | 219 | ``` 220 | GET https://graph.microsoft.com/v1.0/storage/operations/123 221 | ``` 222 | 223 | 224 | The server responds with a "status:succeeded" operation that includes the resource 225 | location: 226 | 227 | ``` 228 | HTTP/1.1 200 OK 229 | 230 | { 231 | "createdDateTime": "2015-06-19T12-01-03.45Z", 232 | "lastActionDateTime": "2015-06-19T12-06-03.0024Z", 233 | "status": "succeeded", 234 | "resourceLocation": "https://graph.microsoft.com/v1.0/storage/archives/987" 235 | } 236 | ``` 237 | 238 | ### Trigger a long running action using the stepwise operation 239 | 240 | ``` 241 | POST https://graph.microsoft.com/v1.0/storage/copyArchive 242 | 243 | { 244 | "displayName": "Image Archive", 245 | "destination": "Second-tier storage" 246 | ... 247 | } 248 | ``` 249 | 250 | The API responds synchronously that the request has been accepted and includes 251 | the Location header with an operation resource for further polling: 252 | 253 | ``` 254 | HTTP/1.1 202 Accepted 255 | 256 | Location: https://graph.microsoft.com/v1.0/storage/operations/123 257 | 258 | ``` 259 | -------------------------------------------------------------------------------- /graph/patterns/namespace.md: -------------------------------------------------------------------------------- 1 | # Namespace 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The namespace pattern provides the ability to organize resource definitions together into a logical set.* 6 | 7 | ## Problem 8 | 9 | When building a complex offering, API designers might need to model many different 10 | resources and their relationships. For a better user experience and 11 | discoverability, related API elements need to be grouped together. 12 | 13 | ## Solution 14 | 15 | API designers can use the namespace attribute of the CSDL schema to declare a 16 | namespace and logically organize related API entities in the Microsoft Graph metadata. 17 | 18 | ```XML 19 | 20 | ... 21 | 22 | ``` 23 | 24 | A public namespace must contain the `microsoft.graph.` prefix and be presented in camel 25 | case; that is, `microsoft.graph.myNamespace`. Elements defined in namespaces not prefixed 26 | with `microsoft.graph` will be mapped to the public `microsoft.graph` namespace. 27 | 28 | Namespaces should not include more than two segments following the `microsoft.graph` prefix; 29 | that is, `microsoft.graph.myNamespace.mySubNamespace`. 30 | 31 | Public namespaces must define an alias, and that alias must be the concatenation of 32 | the segments following the `microsoft.graph` prefix with proper camel casing rules applied; 33 | that is, `myNamespaceMySubNamespace`. 34 | 35 | When type casting is required in the API query, request, or response, a fully 36 | qualified type name is represented as concatenation of the namespace or alias, 37 | followed by a dot (`.`) and the type name. 38 | 39 | ## When to use this pattern 40 | 41 | API resource grouping creates a user-friendly experience, keeping all resources for a specific feature close together and limiting the length of IDE prompts such as auto-complete in some programming languages. 42 | 43 | For a consistent user experience, new namespace should be aligned with a top-level API category. 44 | 45 | ## Issues and considerations 46 | 47 | - Microsoft Graph consistency requirements discourage using the same type names for different concepts even within different namespaces. Microsoft Graph type names must be descriptive and should represent a single concept across the API Surface. 48 | 49 | - A namespace must be consistent with an API category in the navigation path according to [Microsoft Graph REST API Guidelines](../GuidelinesGraph.md#uniform-resource-locators-urls). 50 | 51 | - Changing a namespace prefixed with `microsoft.graph`, or moving types between, into, or out of a namespace prefixed with `microsoft.graph`, is a breaking change. 52 | 53 | - To extend a type in a different schema, a service must declare that schema and the type in it. This is conceptually similar to .NET partial types. 54 | 55 | - To reference a type in a different schema, simply refer to that type by its fully qualified name (namespace + type name). 56 | 57 | - Cyclical references between namespaces are not allowed because many object-oriented languages don’t support cycles between namespaces. 58 | 59 | - Microsoft Graph has some predefined constraints for declared namespaces: 60 | 61 | - All public namespaces must have the prefix `microsoft.graph`. 62 | 63 | - Public namespaces must declare an alias that is the concatenation of the segments following the `microsoft.graph` prefix. 64 | 65 | - At most, two levels of nesting below `microsoft.graph` is recommended. 66 | 67 | - If a namespace does not begin with the `microsoft.graph` prefix, all types in the schema are mapped into the public `microsoft.graph` namespace. 68 | 69 | ## Examples 70 | 71 | ### Namespace and type declarations 72 | 73 | ```XML 74 | ”\> 75 | … 76 | 78 | … 79 | 80 | ``` 81 | 82 | Fully qualified type name: `microsoft.graph.search.bookmark` 83 | 84 | ### Managing multiple schemas 85 | 86 | Workloads must define schemas in their CSDL by using the Edmx format. 87 | Following is an example of a workload that exposes multiple namespaces. 88 | 89 | > **Tip:** As with schemas that exist in the `microsoft.graph` namespace, defining an 90 | entity type is optional; by default your schema derives all entity types 91 | from `microsoft.graph.entity`. 92 | 93 | > **Warning:** Do not deviate from the general structure in the following example. 94 | The schema validation tool expects the XML structure (including XML namespace 95 | declarations) to match this example. 96 | 97 | ```XML 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | ``` -------------------------------------------------------------------------------- /graph/patterns/navigation-property.md: -------------------------------------------------------------------------------- 1 | # Navigation Property 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *A navigation property is used to identify a relationship between resources.* 6 | 7 | ## Problem 8 | -------- 9 | 10 | It is often valuable to represent a relationship between resources in an API. 11 | 12 | Relationships between resources are often implicitly represented by a property contained in one of the resources that provides a key to a related resource. Usually that information is returned in a representation as an id value and the property is named using a convention that identifies the target type of related resource. e.g. userId 13 | 14 | The use of foreign key properties to describe related resources is a weakly typed mechanism and requires additional information for a developer to traverse the relationship. Discovery of related resources is not trivial. 15 | 16 | ## Solution 17 | -------- 18 | 19 | Navigation properties are an [OData convention](https://docs.microsoft.com/en-us/odata/webapi/model-builder-untyped#navigation-property) defined in the [CSDL Specification](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530365) that allows an API designer to describe a special kind of property in a model that references an related entity. In the HTTP API this property name translates to a path segment that can be appended to the URL of the primary resource in order to access a representation of the related resource. This prevents the client from needing to know any additional information on how to construct the URL to the related resource and the client does not need to retrieve the primary resource if it is only interested in the related resource. It is the responsibility of the API implementation to determine the Id of the related resource and return the representation of the related entity. For example: 20 | 21 | - /user/{userId}/manager represents many-to-one relationship 22 | - /user/{userId}/messages represents one-to-many relationship 23 | 24 | Additionally, using the OData Expand query parameter, related entities can be nested into the primary entity so both can be retrieved in a single round trip. 25 | 26 | These relationships can be described in CSDL as follows: 27 | 28 | ```xml 29 | 30 | 31 | 32 | 33 | ``` 34 | 35 | ## Issues and Considerations 36 | ------------------------- 37 | 38 | In the current Microsoft Graph implementation, there are scenarios which use navigation properties that cross backend services that have automatic support; there are also some limitations for other scenarios. These limitations are being eliminated over time, but it will be necessary to ensure support for any particular scenario. [Automatic support and limitations of the current implementation](https://dev.azure.com/msazure/One/_wiki/wikis/Microsoft%20Graph%20Partners/354352/Cross-workload-navigations?anchor=supported-scenarios) are documented internally. 39 |   40 | Navigation properties defined within an entity are not returned by default when retreiving the representation of an entity unless explicity desired by a service. The API can consumer can use the `expand` query parameterm, where supported, to retreive both the source and the target entity of the relationship in a single request. 41 | 42 | Implementing support for accessing the "$ref" of a navigation property allows a caller to return just the URL of related resource. e.g. `/user/23/manager/$ref`. This is useful when a client wishes to identify the related resource but doesn't need all of its properties. 43 | 44 | The strongly-typed nature of navigation properties is valuable for backend services and for client applications, when compared with the weakly-typed foreign key property. 45 | Strong typing allows some documentation and visualizations to be automatically generated, it allows SDK generation, and it allows some automated client code generation; it also prevents the need to store duplicate data on the service side and as a result has improved data consistency across APIs since the duplicate data does not need to be regularly refreshed. 46 | 47 | ## When to Use this Pattern 48 | ------------------------ 49 | 50 | ### "Many-to-one" relationships 51 | 52 | The use of navigation properties is preferred over including an Id field to reference the related entity in a many-to-one relationship. Id values require a client to make two round trips to retrieve the details of a related entity. With a navigation property a client can retrieve a related entity in a single round trip. 53 | 54 | Many-to-one relationships are always non-contained relationships as the lifetime of the target cannot depend on the source. 55 | 56 | ```xml 57 | 58 | 59 | 60 | ``` 61 | 62 | 63 | ### "Zero-or-one-to-one" relationships 64 | 65 | These navigation properties can be used as a structural organization mechanism to separate properties of an entity in a way that is similar to how complex types are often used. The primary difference being that the target of the navigation property are not returned by default when the source entity is retreived. The use of the navigation properties over complex properties is preferred when the source and target information comes from different backend APIs. 66 | 67 | These relationships must be contained. 68 | 69 | ```xml 70 | 71 | 72 | 73 | ``` 74 | 75 | ### "One-to-many" relationships 76 | 77 | Resources that contain a parent Id property in a child resource can utilize a navigation property in the parent resource that is declared as a collection of child resources. If desirable, a parent navigation property can also be created in the child resource to the parent resource. This is usually not necessary as the parent URL is a subset of child resource URL. The main use of this would be when retrieving child resources and choosing to expand properties of the parent resource so that both can be retrieved in a single request. 78 | 79 | `/invoice/{invoiceId}/items/{itemId}?expand=parentInvoice(select=invoiceDate,Customer)` 80 | 81 | ```xml 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ``` 90 | 91 | One-to-many relationships may be contained or non-contained relations. 92 | 93 | 94 | ## Example 95 | ------- 96 | 97 | ### Retrieving a related entity 98 | 99 | ```http 100 | GET /users/{id}/manager?$select=id,displayName 101 | 102 | 200 OK 103 | Content-Type: application/json 104 | 105 | { 106 | "id": "6b3ee805-c449-46a8-aac8-8ff9cff5d213", 107 | "displayName": "Bob Boyce" 108 | } 109 | ``` 110 | 111 | This navigation property could be described with the following CSDL: 112 | ```xml 113 | 114 | 115 | 116 | ``` 117 | `ContainsTarget` is set to false for clarity, this is the default value when the attribute is omitted. 118 | ### Retrieving a reference to a related entity 119 | 120 | ```http 121 | GET /users/{id}/manager/$ref 122 | 123 | 200 OK 124 | Content-Type: application/json 125 | 126 | { 127 | "@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/6b3ee805-c449-46a8-aac8-8ff9cff5d213/Microsoft.DirectoryServices.User" 128 | } 129 | ``` 130 | Note: Currently the base URL returned in $ref results are incorrect. In order to process these URLs the client will need to convert the URL to a Graph URL. 131 | 132 | ### Retrieving an entity with a related entity included 133 | 134 | ```http 135 | GET /users/{id}?select=id,displayName&expand=manager(select=id,displayName) 136 | 137 | 200 OK 138 | Content-Type: application/json 139 | 140 | { 141 | "id": "3f057904-f936-4bf0-9fcc-c1e6f84289d8", 142 | "displayName": "Jim James", 143 | "manager": { 144 | "@odata.type": "#microsoft.graph.user", 145 | "id": "6b3ee805-c449-46a8-aac8-8ff9cff5d213", 146 | "displayName": "Bob Boyce" 147 | } 148 | } 149 | ``` 150 | 151 | ### Creating an entity with a reference to a related entity 152 | 153 | Create a new user that references an existing manager 154 | ```http 155 | POST /users 156 | Content-Type: application/json 157 | 158 | { 159 | "displayName": "Bob", 160 | "manager@odata.bind": "https://graph.microsoft.com/v1.0/users/{managerId}" 161 | } 162 | 163 | 201 Created 164 | ``` 165 | 166 | ### Updating a related entity reference 167 | 168 | Update the user entity to contain a relationship to an existing manager. 169 |   170 | ```http 171 | PATCH /users/{id} 172 | Content-Type: application/json 173 | 174 | { 175 | "displayName": "Bob", 176 | "manager@odata.bind": "https://graph.microsoft.com/v1.0/users/{managerId}" 177 | } 178 | 179 | 204 No Content 180 | ``` 181 |   182 | ### Clear a related entity reference 183 | 184 | Remove the relationship between the user and the manager. 185 |   186 | ```http 187 | DELETE /users/{id}/manager/$ref 188 | 189 | 204 No Content 190 | ``` 191 | 192 | Delete the related entity. 193 | 194 | ```http 195 | DELETE /users/{id}/manager 196 | 197 | 204 No Content 198 | ``` 199 | -------------------------------------------------------------------------------- /graph/patterns/operations.md: -------------------------------------------------------------------------------- 1 | # Operations 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The operations pattern provides the ability to model a change that might impact multiple resources and can't be effectively modeled by using HTTP methods.* 6 | 7 | ## Problem 8 | 9 | Sometimes when modeling a complex business domain, API designers need to model a business operation that effects one or multiple resources and has additional semantic meaning that cannot be expressed by HTTP methods. Modeling the operation via HTTP methods on each individual resource might be either inefficient or expose internal implementation details. 10 | 11 | ## Solution 12 | 13 | To address these use cases, API designers can use operational resources such as functions or actions. If the operation doesn't have any side effects and MUST return a single instance of a type or a collection of instances, then the designer SHOULD use OData functions; otherwise, the designer can model the operation as an action. 14 | 15 | ## When to use this pattern 16 | 17 | The operation pattern might be justified when a modeling operation represents one or combination of the following: 18 | 19 | - a change of a resource (i.e., increment the value of a property) rather than a state (i.e., the final value of the property) 20 | - complex processing logic that shouldn't be exposed to the client 21 | - operation parameters might convey a restricted set of option (i.e., a report that has to specify a date range) 22 | - the operation leverage some service-side data not exposed to (or easily retrieved in context by) the user. 23 | 24 | You can consider related patterns such as [long running operations](./long-running-operations.md) and [change tracking](./change-tracking.md). 25 | 26 | ## Issues and considerations 27 | 28 | - Microsoft Graph does NOT support unbound actions or functions. Bound actions and functions MUST must have the `isBound="true"` attribute and a binding parameter. Bound operations are invoked on resources matching the type of the binding parameter.The first parameter of a bound operation is always the binding parameter.The binding parameter can be of any type, and parameter value MAY be Nullable. 29 | 30 | - Both actions and functions support overloading, meaning a schema might contain multiple actions or functions with the same name. The overload rules as per the OData [standard](http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionOverloads) apply when adding parameters to actions and functions. 31 | 32 | - Because Microsoft Graph only supports bound actions and functions, all must have at least one parameter where the first is the binding parameter. The MUSTS of parameters are as follows: 33 | 34 | - Each parameter must have a simple identifier name. 35 | - The parameter name must be unique within the overload. 36 | - The parameter must specify a type. 37 | 38 | - Microsoft Graph supports the use of optional parameters. The optional parameter annotation can be used instead of creating function or action overloads when unnecessary. 39 | 40 | - API designer **MUST** use POST to call actions on resources. 41 | - API designer **MUST** use GET to call functions on resources. 42 | 43 | - The addition of a new mandatory not-nullable parameter to an existing action or function is a breaking change and is not allowed without proper versioning that is in accordance with our [deprecation guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/graph/deprecation.md). 44 | 45 | ## Examples 46 | 47 | ### A user wants to forward email 48 | 49 | ``` 50 | POST https://graph.microsoft.com/v1.0/me/messages/AQMkADNkMmMxYzIwLWJkOTItNDczZC1hNmYyLWUwZjk2ZTljMDQyNQBGAAAD1dY5iRo4x0_pEqop6hOrQAcAeGCrbYV1-kiG-z9Rv6yHMgAAAgEJAAAAeGCrbYV1-kiG-z9Rv6yHMgABRxeUKgAAAA==/forward 51 | 52 | { 53 | "comment": "FYI", 54 | "toRecipients": [ 55 | { 56 | "emailAddress": { 57 | "address": "alex.darrow@microsoft.com", 58 | "name": "Alex Darrow" 59 | } 60 | } 61 | ] 62 | } 63 | ``` 64 | Response: 65 | ``` 66 | HTTP/1.1 202 Accepted 67 | 68 | "cache-control": "private", 69 | "client-request-id": "ca2d0416-a2c1-05af-df60-0921547a86e9", 70 | "content-length": "0", 71 | "request-id": "8b53016f-cc2b-4d9f-9818-bd6f0a5e3cd0" 72 | ``` 73 | 74 | `forward` operation is modeled as an asynchronous action bound to the Graph `message` entity type because the operation represents a complex business logic processed on the server side. 75 | ``` 76 | 77 | 78 | 79 | 80 | 81 | 82 | ``` 83 | 84 | ### A user wants to see recent application activities 85 | 86 | ``` 87 | GET https://graph.microsoft.com/v1.0/me/activities/recent 88 | ``` 89 | 90 | Response: 91 | 92 | ``` 93 | HTTP/1.1 200 OK 94 | 95 | { 96 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(userActivity)", 97 | "value": [] 98 | } 99 | ``` 100 | `recent` function will query the most recent historyItems and then pull related activities therefore the operation represents a complex business logic processed on the server side. This operation doesn't change any server data and is a good fit for a function. The function is bound to the collection of `userActivity` entity type. 101 | 102 | ``` 103 | 104 | 105 | 106 | 107 | ``` 108 | ### Get a report that provides the number of active users using Microsoft Edge 109 | 110 | ``` 111 | https://graph.microsoft.com/beta/reports/getBrowserUserCounts(period='D7') 112 | ``` 113 | 114 | Response: 115 | 116 | ``` 117 | HTTP/1.1 200 OK 118 | Content-Type: application/json 119 | Content-Length: 205 120 | 121 | { 122 | "value":[ 123 | { 124 | "reportRefreshDate":"2021-04-17", 125 | "reportPeriod":7, 126 | "userCounts":[ 127 | { 128 | "reportDate":"2021-04-17", 129 | "edge":413 130 | }, 131 | { 132 | "reportDate":"2021-04-16", 133 | "edge":883 134 | } 135 | ] 136 | } 137 | ] 138 | } 139 | ``` 140 | 141 | `getBrowserUserCounts` operation doesn't change any server data and is a good fit for a function.`period` operation parameter convey a restricted set of options representing the number of days over which the report is aggregated. The report supports only 7,30,90, or 180 days. In addition the function doesn't return a Graph resource but streams response data in JSON or CSV formats. 142 | 143 | ``` 144 | 145 | 146 | 147 | 148 | 149 | ``` 150 | -------------------------------------------------------------------------------- /graph/patterns/subsets.md: -------------------------------------------------------------------------------- 1 | # Modeling collection subsets 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The modeling collection subsets pattern is the modeling state associated to a collection that may include all instances, an included subset, an excluded subset, no instances, or any combinations of the preceding items.* 6 | 7 | ## Problem 8 | 9 | A common pattern is to apply a policy or state to a collection of resources. With this, there also comes the question of how to model cases where we want to apply to `all` or `none` without having to special case these values within the collection set or introduce cross-property dependencies. Likewise, we'd like to model it in a way where it is easy to understand and interpret usage from just looking at the schema. 10 | 11 | An example is where you have a policy that you need to be able to apply to users in an organization. You might want to support the default **None**, enablement for **All**, or enablement for **Select** users where you only grant it to a few users. 12 | 13 | Existing patterns for this either have special-cased strings or have tightly coupled dependencies between two independent properties. Neither is intuitive, both require reading documentation, and neither can be inferred from the schema or within client libraries. 14 | 15 | ## Solution 16 | 17 | Have an abstract base class where all variants of the subset are derived types from the base subset. For more information, see the [general subtyping guidance](./subtypes.md). 18 | 19 | The abstract base class may also optionally hold an `enum` for the different variants. If it does, the `enum` must have a member for all possible variants. The purpose of including this is to allow for easier ways to do query and filter operations on variants like `all` and `none` without relying on `isof` functions. 20 | 21 | **Base type *without* an enum for the variants** 22 | 23 | ```xml 24 | 25 | ``` 26 | 27 | **Base type *with* an enum for the variants** 28 | 29 | ```xml 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | **Derived types** 43 | 44 | ```xml 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ``` 57 | 58 | Be aware that the name values and types in the preceding examples are just examples and can be replaced with your scenario equivalent values. For example, type names don't really need to be `memberships`. The collection doesn't have to be a collection at all; it can be singular and doesn't have to be a string. 59 | 60 | These pattern type names should satisfy the following naming conventions: 61 | 62 | - The base type name should have the suffix `Base`, and the enumeration type name (if an `enum` is defined) should have the suffix `Kind`. 63 | - Derived child types should have names with enumeration values as the prefixes; for example, if the enumeration member value is `value1`, then the derived type name is `value1`. 64 | 65 | ```xml 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | ``` 82 | 83 | ## When to use this pattern 84 | 85 | Use this pattern when supporting two or more collection states of the following, where at least one of the states is a subset variant: 86 | 87 | - All targets 88 | - No targets 89 | - Subset of targets to be included 90 | - Subset of targets to be excluded 91 | 92 | If you only ever need to support two states—All or None—without using any subsets, it would be better to use a Boolean to toggle on and off. 93 | 94 | ## Issues and considerations 95 | 96 | Given that we are using an overarching subtype model, subtyping model limitations apply here as well; for more details, see the [subtyping documentation](./subtypes.md). 97 | 98 | ## Example 99 | 100 | ```http 101 | GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ 102 | ``` 103 | 104 | _Note: Unrelated properties on entities are omitted for easier readability._ 105 | 106 | ```json 107 | { 108 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#conditionalAccessPolicy", 109 | "values": [ 110 | { 111 | "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", 112 | "conditions": { 113 | "users": { 114 | "includeGuestsOrExternalUsers": { 115 | "externalTenants": { 116 | "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants", 117 | "membershipKind": "all" 118 | } 119 | } 120 | } 121 | } 122 | }, 123 | { 124 | "id": "99d212f4-d94e-cde1-8e3c-208d78238277", 125 | "conditions": { 126 | "users": { 127 | "includeGuestsOrExternalUsers": { 128 | "externalTenants": { 129 | "@odata.type":"microsoft.graph.conditionalAccessEnumeratedExternalTenants", 130 | "membershipKind": "enumerated", 131 | "members": ["bd005e2a-876d-4bf0-92a1-ae9ff4276d54"] 132 | } 133 | } 134 | } 135 | } 136 | } 137 | ] 138 | } 139 | ``` 140 | 141 | ```http 142 | POST https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ 143 | ``` 144 | 145 | _Note: Unrelated properties on entities are omitted for easier readability._ 146 | 147 | ```json 148 | { 149 | "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", 150 | "conditions": { 151 | "users": { 152 | "includeGuestsOrExternalUsers": { 153 | "externalTenants": { 154 | "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants" 155 | } 156 | } 157 | } 158 | } 159 | } 160 | ``` 161 | 162 | or 163 | 164 | ```http 165 | POST https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/ 166 | ``` 167 | 168 | _Note: Unrelated properties on entities are omitted for easier readability._ 169 | 170 | ```json 171 | { 172 | "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", 173 | "conditions": { 174 | "users": { 175 | "includeGuestsOrExternalUsers": { 176 | "externalTenants": { 177 | "@odata.type":"microsoft.graph.conditionalAccessEnumeratedExternalTenants", 178 | "members": ["bd005e2a-876d-4bf0-92a1-ae9ff4276d54"] 179 | } 180 | } 181 | } 182 | } 183 | } 184 | ``` 185 | 186 | ### Filter when base type has the "kind" enum property 187 | 188 | ```http 189 | GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies?$filter=conditions/users/includeGuestsOrExternalUsers/externalTenants/membershipKind eq 'all' 190 | 191 | 200 OK 192 | { 193 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#conditionalAccessPolicy", 194 | "values": [ 195 | { 196 | "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", 197 | "conditions": { 198 | "users": { 199 | "includeGuestsOrExternalUsers": { 200 | "externalTenants": { 201 | "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants", 202 | "membershipKind": "all" 203 | } 204 | } 205 | } 206 | } 207 | } 208 | ] 209 | } 210 | ``` 211 | 212 | ### Filter when base type lacks the "kind" enum property 213 | 214 | ```HTTP 215 | GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies?$filter=isof(conditions/users/includeGuestsOrExternalUsers/externalTenants, microsoft.graph.conditionalAccessAllExternalTenants) 216 | 217 | 200 OK 218 | { 219 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#conditionalAccessPolicy", 220 | "values": [ 221 | { 222 | "id": "66d36273-fe4c-d478-dc22-e0179d856ce7", 223 | "conditions": { 224 | "users": { 225 | "includeGuestsOrExternalUsers": { 226 | "externalTenants": { 227 | "@odata.type":"microsoft.graph.conditionalAccessAllExternalTenants", 228 | "membershipKind": "all" 229 | } 230 | } 231 | } 232 | } 233 | } 234 | ] 235 | } 236 | ``` 237 | -------------------------------------------------------------------------------- /graph/patterns/subtypes.md: -------------------------------------------------------------------------------- 1 | # Type hierarchy 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of resources that have slightly different properties and behavior.* 6 | 7 | ## Problem 8 | 9 | The API design requires that we model a set of resources based on a common concept 10 | that can be further grouped into *mutually exclusive variants* with specific 11 | properties and behaviors. The API design should be evolvable and allow the addition 12 | of new variants without breaking changes. 13 | 14 | ## Solution 15 | 16 | API designers might use a *type hierarchy*, where there is one base 17 | type (which might be abstract) with a few shared properties representing the common concept and one 18 | subtype for each variant of the resource. In the hierarchy, the interdependencies of properties, that is, which properties are relevant for which variants, is fully captured in the type system. 19 | 20 | ## When to use this pattern 21 | 22 | Use this pattern where each variant of a common concept has its own unique properties and behaviors, 23 | no combination of variants is anticipated, and it is acceptable that callers who need to query resources by variant are adequately served by filtering or partitioning using type casting. 24 | 25 | You can consider related patterns such as [facets](./facets.md) and [flat bag of properties](./flat-bag.md). 26 | 27 | ## Issues and considerations 28 | 29 | When introducing a new subtype to the hierarchy, developers need to ensure that 30 | the new subtype doesn't change the semantic of the type hierarchy or collections of the specified base type with implicit constraints. 31 | 32 | To reference properties specific to a derived type, an API request URL might need to include a segment casting to the derived type. If the type hierarchy is very deep, then the resulting URL might become very long and not easily readable. 33 | 34 | There are a few considerations to take into account when new subtypes are introduced: 35 | 36 | - *TODO add something about SDK dependencies and required actions* 37 | - *TODO* Client libraries for a strongly typed language might ignore some of the values 38 | in the @odata.type property without further configuration and need to be 39 | updated to be able to pick the right (client) type to deserialize into. 40 | - In the case of public APIs in GA versions, clients might develop their applications to support exclusively the current set of subtypes, and don’t expect new variations. To mitigate the risk of clients' disruption, when introducing a new subtype, allow ample time for communication and rollout. 41 | 42 | ## Example 43 | 44 | The directoryObject type is the main abstraction for many directory 45 | types such as users, organizational contacts, devices, service principals, 46 | and groups stored in Azure Active Directory. Because any directoryObject object is a unique entity, the directoryObject type itself is derived from the `graph.entity` base type. 47 | 48 | ```XML 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ``` 59 | 60 | Groups and users are derived types and modeled as follows: 61 | 62 | ```XML 63 | 64 | 65 | ... 66 | 67 | 68 | 69 | ... 70 | 71 | ``` 72 | 73 | An API request to get members of a group returns a heterogeneous collection of 74 | users and groups where each element can be a user or a group, and has an 75 | additional `@odata.type` property that specifies the subtype: 76 | 77 | ``` 78 | GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members 79 | 80 | Response payload shortened for readability. The deletedDateTime property from the base type is a non-default property and is only returned if explicitly requested. 81 | 82 | { 83 | "@odata.context": 84 | "https://graph.microsoft.com/v1.0/$metadata#directoryObjects", 85 | "value": [ 86 | { 87 | "@odata.type": "#microsoft.graph.user", 88 | "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", 89 | "jobTitle": "CEO", 90 | ... 91 | }, 92 | { 93 | "@odata.type": "#microsoft.graph.group", 94 | "id": "45f25951-d04f-4c44-b9b0-2a79e915658d", 95 | "description": "Microsoft Graph API Reviewers", 96 | ... 97 | }, 98 | ... 99 | ] 100 | } 101 | ``` 102 | 103 | Addressing a property of the subtype, for example, in `$filter` or `$select`, requires prefixing the property with the fully-qualified name of the subtype (or type derived from the subtype) on which it is defined. To filter on the `jobTitle` for the user type, you need to qualify the property with `microsoft.graph.user`. 104 | 105 | The following query returns all groups that are members of group a94a666e-0367-412e-b96e-54d28b73b2db, as well as users that are members and whose jobTitle is CEO. 106 | 107 | ``` 108 | GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$filter=microsoft.graph.user/jobTitle eq 'CEO' 109 | 110 | Response payload shortened for readability: 111 | 112 | { 113 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects", 114 | "value": [ 115 | { 116 | "@odata.type": "#microsoft.graph.user", 117 | "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", 118 | "jobTitle": "CEO", 119 | ... 120 | }, 121 | { 122 | "@odata.type": "#microsoft.graph.group", 123 | "id": "45f25951-d04f-4c44-b9b0-2a79e915658d", 124 | "description": "Microsoft Graph API Reviewers", 125 | ... 126 | }, 127 | ... 128 | ] 129 | } 130 | ``` 131 | 132 | An entire collection can be cast to a particular subtype by appending the fully-qualified subtype name to the URL. Doing so filters the collection to members of (or derived from) that particular subtype, and makes the properties of that subtype available without casting. In this case, the `@odata.type` attribute is not returns for records of the specified subtype because the `@odata.context` indicates that the entire collection is consists of the particular subtype. Types derived from that subtype do still have the `@odata.type` attribute. 133 | 134 | The following query returns only users that are members of group a94a666e-0367-412e-b96e-54d28b73b2db and whose jobTitle is CEO. 135 | 136 | ``` 137 | GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$filter=jobTitle eq 'CEO' 138 | 139 | Response payload shortened for readability: 140 | 141 | { 142 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", 143 | "value": [ 144 | { 145 | "id": "37ca648a-a007-4eef-81d7-1127d9be34e8", 146 | "jobTitle": "CEO", 147 | ... 148 | }, 149 | ... 150 | ] 151 | } 152 | ``` 153 | 154 | An API request to create a subtype object in a polymorphic collection requires "@odata.type" specified in the request body. 155 | 156 | ``` 157 | POST https://graph.microsoft.com/v1.0/directoryObjects 158 | 159 | { 160 | "@odata.type": "#microsoft.graph.group", 161 | "description": "Microsoft Graph API Reviewers", 162 | ... 163 | } 164 | ``` 165 | -------------------------------------------------------------------------------- /graph/patterns/upsert.md: -------------------------------------------------------------------------------- 1 | # Upsert 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | *The `Upsert` pattern is a non-destructive idempotent operation using a client-provided key, that ensures that system resources can be deployed in a reliable, repeatable, and controlled way, typically used in Infrastructure as Code (IaC) scenarios.* 6 | 7 | ## Problem 8 | 9 | Infrastructure as code (IaC) defines system resources and topologies in a declarative manner that allows teams to manage those resources as they would code. 10 | Practicing IaC helps teams deploy system resources in a reliable, repeatable, and controlled way. 11 | IaC also helps automate deployment and reduces the risk of human error, especially for complex large environments. 12 | Customers want to adopt IaC practices for many of the resources managed through Microsoft Graph. 13 | 14 | Most resources' creation operations in Microsoft Graph are not idempotent in nature. 15 | As a consequence, API consumers that want to offer IaC solutions, must create compensation layers that can mimic idempotent behavior. 16 | For example, when creating a resource, the compensation layer must check whether the resource first exists, before trying to create or update the resource. 17 | 18 | Additionally, IaC code scripts or templates usually employ client-provided names (or keys) to track resources in a predictable manner, whereas [Microsoft Graph guidelines](../GuidelinesGraph.md#behavior-modeling) suggests use of `POST` to create new entities with service-generated keys. 19 | 20 | ## Solution 21 | 22 | The solution is to use an `Upsert` pattern, to solve for the non-idempotent creation and client-provided naming problems. 23 | 24 | * `Upsert` uses `PATCH` with a client-provided key in the URL: 25 | * If there is a natural client-provided key that can serve as the primary key, then the service should support `Upsert` with that key. 26 | * If the primary key is service-generated, the client-provided key should use an [alternate key](./alternate-key.md) to support idempotent creation. 27 | * For a non-existent resource (specified by the client-provided key) the service must handle this as a "create" (aka insert). As part of creation, the service must still generate the primary key value, if appropriate. 28 | * For an existing resource (specified by the client-provided key) the service must handle this as an "update". 29 | * If using an alternate key, then 30 | * for IaC scenarios, the alternate key should be called `uniqueName`, if there isn't already a more natural existing property that could be used as an alternate key. 31 | * the service must also support `GET` using the alternate key pattern. 32 | * Services should always support `POST` to the collection URL. 33 | * For service-generated keys, this should return the server generated key. 34 | * For client-provided keys, the client can provide the key as part of the request payload. 35 | * If a service does not support `Upsert`, then a `PATCH` call against a non-existent resource must result in an HTTP "404 not found" error. 36 | 37 | This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `Upsert` without impacting existing apps or functionality. 38 | 39 | Ideally, all new entity types should support an `Upsert` mechanism, especially where they support control-plane APIs, or are used in admin style or IaC scenarios. 40 | 41 | ## When to use this pattern 42 | 43 | This pattern should be adopted for resources that are managed through infrastructure as code or desired state configuration. 44 | 45 | ## Issues and considerations 46 | 47 | * Services with existing APIs that use a client-defined key that want to start supporting the `Upsert` pattern may have concerns about backwards compatibility. 48 | API producers can require clients to opt-in to the `Upsert` pattern, by using the `Prefer: create-if-missing` HTTP request header. 49 | * `Upsert` can also be supported against singletons, using a `PATCH` to the singleton's URL. 50 | * Services that support `Upsert` should allow clients to use the: 51 | * `If-Match=*` request header to explicitly treat an `Upsert` request as an update and not an insert. 52 | * `If-None-Match=*` request header to explicitly treat an `Upsert` request as an insert and not an update. 53 | * The client-provided alternate key must be immutable after being set. If its value is null then it should be settable as a way to backfill existing resources for use in IaC scenarios. 54 | * API producers could use `PUT` operations to create or update, but generally this approach is not recommended due to the destructive nature of `PUT`'s replace semantics. 55 | * API producers may annotate entity sets, singletons and collections to indicate that entities can be "upserted". The example below shows this annotation for the `groups` entity set. 56 | 57 | ```xml 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ``` 66 | 67 | ## Examples 68 | 69 | For these examples we'll use the `group` entity type, which defines both a primary (service-generated) key (`id`) and an alternate (client-provided) key (`uniqueName`). 70 | 71 | ```xml 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | ``` 96 | 97 | ### Upserting a record (creation path) 98 | 99 | Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist. 100 | 101 | ```http 102 | PATCH /groups(uniqueName='Group157') 103 | Prefer: return=representation 104 | ``` 105 | 106 | ```json 107 | { 108 | "displayName": "My favorite group", 109 | "description": "All my favorite people in the world" 110 | } 111 | ``` 112 | 113 | Response: 114 | 115 | ```http 116 | 201 created 117 | Preference-Applied: return=representation 118 | ``` 119 | 120 | ```json 121 | { 122 | "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", 123 | "displayName": "My favorite group", 124 | "description": "All my favorite people in the world", 125 | "uniqueName": "Group157" 126 | } 127 | ``` 128 | 129 | ### Upserting a record (update path) 130 | 131 | Create a new group, with a `uniqueName` of "Group157", exactly like before. Except in this case, this group already exists. This is a common scenario in IaC, when a deployment template is re-run multiple times. 132 | 133 | ```http 134 | PATCH /groups(uniqueName='Group157') 135 | Prefer: return=representation 136 | ``` 137 | 138 | ```json 139 | { 140 | "displayName": "My favorite group", 141 | "description": "All my favorite people in the world" 142 | } 143 | ``` 144 | 145 | Response: 146 | 147 | ```http 148 | 200 ok 149 | Preference-Applied: return=representation 150 | ``` 151 | 152 | ```json 153 | { 154 | "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", 155 | "displayName": "My favorite group", 156 | "description": "All my favorite people in the world", 157 | "uniqueName": "Group157" 158 | } 159 | ``` 160 | 161 | Notice how this operation is idempotent in nature, rather than returning a 409 conflict error. 162 | 163 | ### Updating a record 164 | 165 | Update "Group157" group with a new description. 166 | 167 | ```http 168 | PATCH /groups(uniqueName='Group157') 169 | Prefer: return=representation 170 | ``` 171 | 172 | ```json 173 | { 174 | "description": "Some of my favorite people in the world." 175 | } 176 | ``` 177 | 178 | Response: 179 | 180 | ```http 181 | 200 ok 182 | Preference-Applied: return=representation 183 | ``` 184 | 185 | ```json 186 | { 187 | "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", 188 | "displayName": "My favorite group", 189 | "description": "Some of my favorite people in the world.", 190 | "uniqueName": "Group157" 191 | } 192 | ``` 193 | 194 | ### Upsert opt-in request 195 | 196 | In this case, the group API is a pre-existing API that supports `PATCH` with a client-provided alternate key. To enable `Upsert` behavior, 197 | the client must opt-in using an HTTP request header, to create a new group using `PATCH`. 198 | 199 | ```http 200 | PATCH /groups(uniqueName='Group157') 201 | Prefer: create-if-missing; return=representation 202 | ``` 203 | 204 | ```json 205 | { 206 | "displayName": "My favorite group", 207 | "description": "All my favorite people in the world" 208 | } 209 | ``` 210 | 211 | Response: 212 | 213 | ```http 214 | 201 created 215 | Preference-Applied: create-if-missing; return=representation 216 | ``` 217 | 218 | ```json 219 | { 220 | "id": "1a89ade6-9f59-4fea-a139-23f84e3aef66", 221 | "displayName": "My favorite group", 222 | "description": "All my favorite people in the world", 223 | "uniqueName": "Group157" 224 | } 225 | ``` 226 | 227 | ### Upsert (create) not supported 228 | 229 | Following on from the last example, the same request to create a new group, with a `uniqueName` of "Group157", 230 | without the opt-in header, results in a 404 HTTP response code. 231 | 232 | ```http 233 | PATCH /groups(uniqueName='Group157') 234 | Prefer: return=representation 235 | ``` 236 | 237 | ```json 238 | { 239 | "displayName": "My favorite group", 240 | "description": "All my favorite people in the world" 241 | } 242 | ``` 243 | 244 | Response: 245 | 246 | ```http 247 | 404 not found 248 | ``` 249 | -------------------------------------------------------------------------------- /graph/patterns/viewpoint.md: -------------------------------------------------------------------------------- 1 | # Viewpoint 2 | 3 | Microsoft Graph API Design Pattern 4 | 5 | 6 | *The viewpoint pattern provides the ability to manage properties of a shared object that have different values for different users.* 7 | 8 | ## Problem 9 | A shared resource, such as a website or a group message, may have different states for different users who access it at different times in an organizational context. For example, user1 may read and delete a message, while user2 may not have seen it yet. This usually happens when a shared item is presented in an individual context. 10 | ## Solution 11 | 12 | The viewpoint pattern provides a solution to how to model an individual user context on a shared resource using a `viewpoint` structural property on an API entity type. 13 | For example, the `viewpoint` property can indicate whether a message is read, deleted, or flagged for a given user. 14 | The consistent naming convention ensures that when a developer uses Graph APIs all `viewpoint` structural properties represent type specific user context across different M365 services and features. 15 | 16 | This pattern simplifies the API client logic by hiding the state transition details and providing state persistency on the server side. The server can manage the different viewpoints for the shared resource without exposing additional complexity to the client. To support queries for a user state the `viewpoint` property should support filtering. 17 | ## Issues and considerations 18 | 19 | - Because the `viewpoint` property reflects an individual user's context, it is null when accessed with application permissions. 20 | - Sometimes, the viewpoint can be computed on the server. In this case, an API producer should add OData annotations to the property to provide more information for downstream tools, such as SDKs and documentation generation. 21 | ``` 22 | 23 | 24 | 25 | ``` 26 | - An alternative to this design would be to store the user state on the client side. However, this may be problematic in some cases, because of the many devices that a user may have and the need to synchronize the state across them. 27 | - Often, updating the `viewpoint` property may cause a side effect, so you might consider an OData action to do the update. For some user scenarios, the `PATCH` method could be a better way to update a `viewpoint`. 28 | 29 | ## Examples 30 | 31 | ### Defining a viewpoint 32 | 33 | The following example demonstrates how to define the 'viewpoint' property for the `chat` entity, where a chat is a collection of chatMessages between one or more participants: 34 | ``` 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ... 47 | 48 | 49 | 50 | ``` 51 | ### Reading an entity with a viewpoint 52 | 53 | The following example shows reading a collection of chats for an identified user, with a viewpoint for each chat: 54 | 55 | ```http 56 | GET https://graph.microsoft.com/v1.0/users/8b081ef6-4792-4def-b2c9-c363a1bf41d5/chats 57 | ``` 58 | 59 | ```http 60 | 61 | HTTP/1.1 200 OK 62 | Content-type: application/json 63 | ``` 64 | 65 | ``` 66 | { 67 | "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#chats", 68 | "@odata.count": 3, 69 | "value": [ 70 | { 71 | "id": "19:meeting_MjdhNjM4YzUtYzExZi00OTFkLTkzZTAtNTVlNmZmMDhkNGU2@thread.v2", 72 | "topic": "Meeting chat sample", 73 | "createdDateTime": "2020-12-08T23:53:05.801Z", 74 | "lastUpdatedDateTime": "2022-12-08T23:58:32.511Z", 75 | "chatType": "meeting", 76 | "viewpoint":{ 77 | "lastMessageReadDateTime": "2021-03-28T21:10:00.000Z" 78 | } 79 | }, 80 | { 81 | "id": "19:561082c0f3f847a58069deb8eb300807@thread.v2", 82 | "topic": "Group chat sample", 83 | "createdDateTime": "2020-12-03T19:41:07.054Z", 84 | "lastUpdatedDateTime": "2020-12-08T23:53:11.012Z", 85 | "chatType": "group", 86 | "viewpoint":{ 87 | "lastMessageReadDateTime": "0000-01-01T00:00:00.000Z" 88 | } 89 | } 90 | ] 91 | } 92 | ``` 93 | ### Updating a viewpoint using an action 94 | 95 | The following example shows marking a chat `viewpoint` as read for a user using an action: 96 | 97 | ```http 98 | 99 | POST https://graph.microsoft.com/beta/chats/19:7d898072-792c-4006-bb10-5ca9f2590649_8ea0e38b-efb3-4757-924a-5f94061cf8c2@unq.gbl.spaces/markChatReadForUser 100 | 101 | { 102 | "user": { 103 | "id" : "d864e79f-a516-4d0f-9fee-0eeb4d61fdc2", 104 | "tenantId": "2a690434-97d9-4eed-83a6-f5f13600199a" 105 | } 106 | } 107 | ``` 108 | 109 | The server responds with a success status code and no payload: 110 | 111 | ```http 112 | HTTP/1.1 204 No Content 113 | ``` 114 | ### Updating a viewpoint using `PATCH` method 115 | 116 | The following example shows how to mark a topic with the `viewpoint` label as reviewed for a user by using the `PATCH` method (this example does not represent an actual API, but only an illustration): 117 | 118 | ```http 119 | PATCH https://graph.microsoft.com/beta/sampleTopics/19:7d898072-792c-4006-bb10-5ca9f259 120 | 121 | { 122 | "title": "Announcements: Changes to PowerPoint and Word to open files faster", 123 | ... 124 | "viewpoint": { 125 | "isReviewed" : "true" 126 | } 127 | } 128 | ``` 129 | 130 | The server responds with a success status code and no payload: 131 | 132 | ```http 133 | HTTP/1.1 204 No Content 134 | ``` 135 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution 4.0 International License. 2 | To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 3 | --------------------------------------------------------------------------------