├── .catwatch.yaml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .markdownlint.yaml ├── .zappr.yaml ├── BUILD.adoc ├── CONTRIBUTING.adoc ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.adoc ├── assets └── api-zalando-small.jpg ├── chapters ├── api-operation.adoc ├── best-practices.adoc ├── changelog.adoc ├── compatibility.adoc ├── data-formats.adoc ├── deprecation.adoc ├── design-principles.adoc ├── events.adoc ├── general-guidelines.adoc ├── http-headers.adoc ├── http-requests.adoc ├── http-status-codes-and-errors.adoc ├── hyper-media.adoc ├── introduction.adoc ├── json-guidelines.adoc ├── meta-information.adoc ├── pagination.adoc ├── performance.adoc ├── references.adoc ├── security.adoc ├── tooling.adoc └── urls.adoc ├── index.adoc ├── models ├── headers-1.0.0.yaml ├── money-1.0.0.yaml ├── problem-1.0.0.yaml ├── problem-1.0.1.yaml ├── request-headers-1.0.0.yaml └── response-headers-1.0.0.yaml ├── resources ├── fonts │ ├── Noto_Emoji │ │ ├── NotoEmoji-Bold.ttf │ │ ├── NotoEmoji-Light.ttf │ │ ├── NotoEmoji-Medium.ttf │ │ ├── NotoEmoji-Regular.ttf │ │ ├── NotoEmoji-SemiBold.ttf │ │ ├── NotoEmoji-VariableFont_wght.ttf │ │ ├── OFL.txt │ │ └── README.txt │ ├── Noto_Serif │ │ ├── NotoSerif-Bold.ttf │ │ ├── NotoSerif-BoldItalic.ttf │ │ ├── NotoSerif-Italic.ttf │ │ ├── NotoSerif-Regular.ttf │ │ └── OFL.txt │ └── Ubuntu │ │ ├── Ubuntu-Bold.ttf │ │ ├── Ubuntu-BoldItalic.ttf │ │ ├── Ubuntu-Italic.ttf │ │ ├── Ubuntu-Light.ttf │ │ ├── Ubuntu-LightItalic.ttf │ │ ├── Ubuntu-Medium.ttf │ │ ├── Ubuntu-MediumItalic.ttf │ │ └── Ubuntu-Regular.ttf └── themes │ └── pdf-theme.yml ├── sass ├── settings │ └── _zalando.scss └── zalando.scss ├── scripts ├── build-css.sh ├── create-zally-issue.sh ├── generate-includes.sh └── generate-rules-json.sh └── zalando.css /.catwatch.yaml: -------------------------------------------------------------------------------- 1 | title: Zalando's Restful API Guidelines 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the main branch 5 | on: 6 | pull_request: 7 | branches: [ main ] 8 | push: 9 | branches: [ main ] 10 | 11 | jobs: 12 | build_docs: 13 | name: Build documentation 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: | 20 | make check-rules > check-rule-ids.log 21 | make all 22 | 23 | - name: Get Github Token 24 | id: get-gh-token 25 | if: ${{ github.ref == 'refs/heads/main' }} 26 | uses: vadeg/github-app-token-action@v0.0.1 27 | with: 28 | app_private_key: ${{ secrets.APP_PRIVATE_KEY }} 29 | application_id: ${{ secrets.APP_ID }} 30 | 31 | - name: Create Zally Issues 32 | if: ${{ github.ref == 'refs/heads/main' }} 33 | env: 34 | GH_TOKEN: ${{ steps.get-gh-token.outputs.gh_access_token }} 35 | run: | 36 | scripts/create-zally-issue.sh 37 | 38 | - name: Deploy to GitHub Pages 39 | if: ${{ github.ref == 'refs/heads/main' }} 40 | uses: peaceiris/actions-gh-pages@v3 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | publish_dir: ./output 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .sass-cache/ 3 | output/ 4 | includes/ 5 | .project 6 | .idea 7 | .metals 8 | .vscode 9 | .DS_Store -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # General rules 3 | # 4 | 5 | # MD012/no-multiple-blanks - Multiple consecutive blank lines 6 | MD012: 7 | # Consecutive blank lines 8 | maximum: 3 9 | 10 | # MD033/no-inline-html - Not part of our style guideline 11 | MD033: false 12 | 13 | # 14 | # Rules conflicting with ascii text. 15 | # 16 | 17 | # MD034/no-bare-urls - Bare URL used 18 | MD034: false 19 | 20 | # MD037/no-space-in-emphasis - used as second level list 21 | MD037: false 22 | 23 | # MD041/first-line-heading/first-line-h1 - chapters are on level h2 24 | MD041: false 25 | 26 | # MD049/emphasis-style - asciidoc uses different styles 27 | MD049: false 28 | 29 | # 30 | # Rules that may be applied when sticking to markdown code blocks. 31 | # 32 | 33 | # MD004/ul-style 34 | MD004: false 35 | 36 | # MD022/blanks-around-headings/blanks-around-headers 37 | MD022: false 38 | 39 | # MD023/heading-start-left/header-start-left 40 | MD023: false 41 | 42 | # MD026/no-trailing-punctuation 43 | MD026: false 44 | 45 | # 46 | # Rules that are unclear whether they may be applied - may depend also on 47 | # markdown code blocks. 48 | # 49 | 50 | # MD003/heading-style/header-style 51 | MD003: false 52 | 53 | # MD024/no-duplicate-heading 54 | MD024: false 55 | 56 | # MD027/no-multiple-space-blockquote 57 | MD027: false 58 | 59 | # MD052/reference-links-images 60 | MD052: false 61 | -------------------------------------------------------------------------------- /.zappr.yaml: -------------------------------------------------------------------------------- 1 | X-Zalando-Team: architecture 2 | X-Zalando-Type: doc 3 | approvals: 4 | groups: 5 | zalando: 6 | minimum: 2 7 | from: 8 | orgs: 9 | - zalando 10 | collaborators: true 11 | -------------------------------------------------------------------------------- /BUILD.adoc: -------------------------------------------------------------------------------- 1 | = Build 2 | 3 | == Build and test HTML site 4 | 5 | [source,bash] 6 | ---- 7 | make all 8 | ---- 9 | 10 | **Note:** While we have removed the `epub` and `pdf` from our published pages, 11 | the targets are still available for experimenting. 12 | 13 | Since http://asciidoctor.org/docs[asciidoctor] does not support a linter yet, 14 | we have setup https://unpkg.com/browse/markdownlint-cli@0.27.1/README.md[markdownlint] 15 | as an experiment to support coding standards. To install and check for 16 | violations use 17 | 18 | [source,bash] 19 | ---- 20 | make lint 21 | ---- 22 | 23 | If linting with the suggested rules proves helpful we will integrate this 24 | target into the a `pre-commit`-hook to enforce a common style and append it 25 | to the `all`-target. 26 | 27 | **Note:** We have currently not tested the capability of `markdownlint` (or 28 | `make format`) to fix linter violations. 29 | 30 | == Watch for changes and rebuild 31 | 32 | [source,bash] 33 | ---- 34 | make watch 35 | ---- 36 | 37 | It uses https://github.com/watchexec/watchexec[watchexec] to watch for 38 | changes in the `.adoc` (and `.css`) files to rebuild the html on save. 39 | 40 | == Find next Rule ID 41 | 42 | If you want to create a new rule, you need to define a new rule identifier. Use 43 | the following command to find the next unused rule identifier. 44 | 45 | [source,bash] 46 | ---- 47 | make next-rule-id 48 | ---- 49 | 50 | == Generate Custom CSS 51 | 52 | In order to generate custom CSS we have to use 53 | http://asciidoctor.org/docs/user-manual/#stylesheet-factory[`stylesheet-factory`] 54 | 55 | This script clones the factory repository, installs the dependencies and 56 | generates CSS based in the `zalando.scss` 57 | 58 | [source,bash] 59 | ---- 60 | .scripts/build-css.sh 61 | ---- -------------------------------------------------------------------------------- /CONTRIBUTING.adoc: -------------------------------------------------------------------------------- 1 | = Contributing 2 | 3 | == When to contribute? 4 | 5 | === Minor improvements 6 | 7 | Minor improvements like grammar, spelling or link fixes are always welcome – both 8 | from Zalando employees and from outsiders. 9 | 10 | Just open a pull request. The maintainers will review it and usually merge. 11 | If you see something wrong, but don't know how to fix it, please open an issue. 12 | 13 | === Major rule changes 14 | 15 | For major changes to an existing rule (or adding a new rule), a discussion 16 | and decision by Zalando's API guild is needed. 17 | 18 | For this, we generally only consider arguments which also apply to Zalando's 19 | situation, as these are Zalando's guidelines, not generic guidelines for everyone. 20 | 21 | So such changes typically (but not exclusively) will be suggested by Zalando employees. 22 | Feel free to open an issue first outlining the need, or (if you are an employee) contact us in our 23 | internal #guild-api chat, or come to one of our bi-weekly meetings to discuss this. 24 | 25 | After we have some general alignment on what kind of rule (change) we want, 26 | someone will be assigned to write a PR. We welcome volunteers (please note 27 | in your issue that you volunteer, if you can't be in the meeting). 28 | 29 | You may also directly create a PR, but to avoid spending time on big changes 30 | which won't be accepted, we recommend to start with an issue. 31 | 32 | == How to Contribute? 33 | 34 | === Pull requests only 35 | 36 | *DON'T* push to the master branch directly. _(This mainly applies to people with write/admin access.)_ Always use pull requests and 37 | let people discuss changes in pull request. 38 | 39 | Pull requests should only be merged after all discussions have been 40 | concluded and at least 2 reviewers gave their approvals. 41 | _(This should be automatically enforced by Zalando's ComPR tool, but if it 42 | doesn't work, please don't circumvent it.)_ 43 | 44 | === Changelog for major changes 45 | 46 | When your pull request does major changes, please also add an entry to 47 | the changelog. 48 | 49 | === Rule IDs 50 | 51 | Each of the rules has a unique ID. A Rule ID is immutable and durable. It 52 | doesn't change until the content of the rule changes significantly so that 53 | it does not make sense to identify it with the old Rule ID. 54 | 55 | In order to ensure the uniqueness of the IDs you can use `make check-rules`. 56 | Preferably, you add this command as a `pre-commit` hook to your local 57 | repository. 58 | 59 | To generate a new unique Rule ID simply run `make next-rule-id`. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public 379 | licenses. Notwithstanding, Creative Commons may elect to apply one of 380 | its public licenses to material it publishes and in those instances 381 | will be considered the "Licensor." The text of the Creative Commons 382 | public licenses is dedicated to the public domain under the CC0 Public 383 | Domain Dedication. Except for the limited purpose of indicating that 384 | material is shared under a Creative Commons public license or as 385 | otherwise permitted by the Creative Commons policies published at 386 | creativecommons.org/policies, Creative Commons does not authorize the 387 | use of the trademark "Creative Commons" or any other trademark or logo 388 | of Creative Commons without its prior written consent including, 389 | without limitation, in connection with any unauthorized modifications 390 | to any of its public licenses or any other arrangements, 391 | understandings, or agreements concerning use of licensed material. For 392 | the avoidance of doubt, this paragraph does not form part of the 393 | public licenses. 394 | 395 | Creative Commons may be contacted at creativecommons.org. 396 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Paul Ebermann 2 | Thomas Frauenstein 3 | Tronje Krop 4 | Miha Lunar 5 | 6 | Former Maintainers: 7 | Sean Floyd 8 | Felix Müller 9 | Christoph Korn 10 | Tim Lossen 11 | Michael Duergner 12 | Maxim Tschumak 13 | Willi Schönborn 14 | Bill de Hóra 15 | Andreas Schmidt 16 | Vadim Shaigorodskiy 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | DOCKER ?= $(shell command -v docker || command -v podman || docker) 3 | ASCIIDOC := asciidoctor/docker-asciidoctor:latest 4 | DIRMOUNTS := /documents 5 | DIRCONTENTS := chapters 6 | DIRSCRIPTS := scripts 7 | DIRBUILDS := output 8 | DIRINCLUDES := includes 9 | DIRWORK := $(shell pwd -P) 10 | 11 | 12 | .PHONY: all clean install lint format pull assets rules html pdf epub force 13 | .PHONY: check check-rules check-rules-duplicates check-rules-incorrects 14 | .PHONY: next-rule-id watch 15 | 16 | all: clean html rules 17 | clean: 18 | rm -rf $(DIRBUILDS) $(DIRINCLUDES); 19 | 20 | install: $(NVM_BIN)/markdownlint 21 | $(NVM_BIN)/markdownlint: 22 | npm install --global markdownlint-cli; 23 | lint: $(NVM_BIN)/markdownlint 24 | markdownlint --config .markdownlint.yaml chapters/*.adoc; 25 | format: $(NVM_BIN)/markdownlint 26 | markdownlint --config .markdownlint.yaml --fix chapters/*.adoc; 27 | 28 | pull: 29 | $(DOCKER) pull $(ASCIIDOC); 30 | 31 | check: check-rules 32 | check-rules: check-rules-duplicates check-rules-incorrects 33 | check-rules-duplicates: 34 | @DUPLICATED="$$(grep -roE "\[#[0-9]+]" $(DIRCONTENTS) | sort |uniq -d)"; \ 35 | if [ -n "$${DUPLICATED}" ]; then \ 36 | echo "Duplicated Rule IDs: $$(echo "$${DUPLICATED}" | tr -d '\n')"; \ 37 | echo "Please make sure the Rule ID anchors are unique"; \ 38 | exit 1; \ 39 | fi; 40 | 41 | check-rules-incorrects: 42 | @INCORRECT="$$(grep -rnE "(\[[0-9]{1,4}\]|\[ +#?[0-9]+ +\])" $(DIRCONTENTS))"; \ 43 | if [ -n "$${INCORRECT}" ]; then \ 44 | echo -e "Incorrect Rule IDs:\n $${INCORRECT}"; \ 45 | echo "Please make sure that the Rule ID anchors conform to '\[#[0-9]+\]'"; \ 46 | exit 1; \ 47 | fi; 48 | 49 | next-rule-id: 50 | @IFS=$$'\r\n' GLOBIGNORE='*' command eval \ 51 | "RULE_IDS=($$(grep -rh "^.*\[#[0-9]\{1,5\}.*$$" $(DIRCONTENTS) | sort -r))"; \ 52 | echo $$(($$(echo $${RULE_IDS[0]} | tr -d '\[' | tr -d '\]' | tr -d '#') + 1)); 53 | 54 | assets: 55 | mkdir -p $(DIRBUILDS); 56 | cp -r assets $(DIRBUILDS)/; 57 | cp -r models $(DIRBUILDS)/; 58 | cp -r models/{problem-1.0.{0,1},money-1.0.0}.yaml $(DIRBUILDS); 59 | 60 | rules: check-rules 61 | $(DIRSCRIPTS)/generate-rules-json.sh | \ 62 | jq -s '{rules: . | sort}' | tee $(DIRBUILDS)/rules >$(DIRBUILDS)/rules.json; 63 | 64 | $(DIRINCLUDES): models/headers-1.0.0.yaml $(DIRSCRIPTS)/generate-includes.sh 65 | mkdir -p $(DIRINCLUDES); $(DIRSCRIPTS)/generate-includes.sh "$(DIRINCLUDES)"; 66 | 67 | html: $(DIRINCLUDES) check assets pull 68 | $(DOCKER) run --interactive --user=$$(id -u):$$(id -g) \ 69 | --volume=$(DIRWORK):$(DIRMOUNTS)/ \ 70 | $(ASCIIDOC) asciidoctor -D $(DIRMOUNTS)/$(DIRBUILDS) index.adoc; 71 | 72 | watch: 73 | watchexec --exts adoc,css --ignore output -r make html 74 | 75 | # Not used any longer. 76 | pdf: $(DIRINCLUDES) check pull 77 | $(DOCKER) run --interactive --user=$$(id -u):$$(id -g) \ 78 | --volume=$(DIRWORK):$(DIRMOUNTS)/ \ 79 | $(ASCIIDOC) asciidoctor-pdf -v \ 80 | -a pdf-fontsdir=$(DIRMOUNTS)/resources/fonts \ 81 | -a pdf-theme=$(DIRMOUNTS)/resources/themes/pdf-theme.yml \ 82 | -D $(DIRMOUNTS)/$(DIRBUILDS) index.adoc; 83 | mv -f $(DIRBUILDS)/index.pdf $(DIRBUILDS)/zalando-guidelines.pdf; 84 | 85 | # Not used any longer. 86 | epub: $(DIRINCLUDES) check pull 87 | $(DOCKER) run --interactive --user=$$(id -u):$$(id -g) \ 88 | --volume=$(DIRWORK):$(DIRMOUNTS)/ \ 89 | $(ASCIIDOC) asciidoctor-epub3 -D $(DIRMOUNTS)/$(DIRBUILDS) index.adoc; 90 | mv -f $(DIRBUILDS)/index.epub $(DIRBUILDS)/zalando-guidelines.epub; 91 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Developing Restful APIs: A Comprehensive Set of Guidelines by Zalando 2 | 3 | 4 | https://github.com/zalando/restful-api-guidelines/actions/[image:https://github.com/zalando/restful-api-guidelines/actions/workflows/build.yml/badge.svg[Build status]] 5 | Latest published version: 6 | http://zalando.github.io/restful-api-guidelines/[*HTML*], 7 | 8 | == Purpose 9 | 10 | Great RESTful APIs look like they were designed by a single team. This 11 | promotes API adoption, reduces friction, and enables clients to use them 12 | properly. To build APIs that meet this standard, and to answer many 13 | common questions encountered along the way of RESTful API development, 14 | the Zalando Tech team has created this comprehensive set of guidelines. 15 | We have shared it with you to inspire additional discussion and 16 | refinement within and among your teams, and contribute our learnings and 17 | suggestions to the tech community at large. 18 | 19 | == Usage 20 | 21 | Feel free to use these guidelines as a guidance for your own 22 | development. Note that we encourage our own teams to use them in order 23 | to challenge their APIs. As such, you should consider this to be a 24 | living, evolving document. We will revise and update based on our 25 | learnings and experiences. 26 | 27 | See link:BUILD.adoc[BUILD documentation] for technical details. 28 | 29 | == License 30 | 31 | We have published these guidelines under the CC-BY (Creative commons 32 | Attribution 4.0) license. Please see link:LICENSE[LICENSE file]. 33 | -------------------------------------------------------------------------------- /assets/api-zalando-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/assets/api-zalando-small.jpg -------------------------------------------------------------------------------- /chapters/api-operation.adoc: -------------------------------------------------------------------------------- 1 | [[api-operation]] 2 | = REST Operation 3 | 4 | 5 | [#192] 6 | == {MUST} publish OpenAPI specification for non-component-internal APIs 7 | 8 | All service applications must publish OpenAPI specifications of their external 9 | APIs. While this is optional for internal APIs, i.e. APIs marked with the 10 | *component-internal* <<219, API audience>> group, we still recommend to do so 11 | to profit from the API management infrastructure. 12 | 13 | An API is published by copying its *OpenAPI specification* into the reserved 14 | */zalando-apis* directory of the *deployment artifact* used to deploy the 15 | provisioning service. The directory must only contain *self-contained YAML* 16 | *files* that each describe one API (exception see <<234>>). We prefer this 17 | deployment artifact-based method over the past (now legacy) 18 | `.well-known/schema-discovery` service endpoint-based publishing process, that 19 | we only support for backward compatibility reasons. 20 | 21 | Background: In our dynamic and complex service infrastructure, it is important 22 | to provide API client developers a central place with online access to the API 23 | specifications of all running applications. As a part of the infrastructure, 24 | the API publishing process is used to detect API specifications. The findings 25 | are published in the API Portal - the universal hub for all Zalando APIs. 26 | 27 | *Note:* To publish an API, it is still necessary to deploy the artifact 28 | successful, as we focus the discovery experience on APIs supported by running 29 | services. 30 | 31 | 32 | [#193] 33 | == {SHOULD} monitor API usage 34 | 35 | Owners of APIs used in production should monitor API service to get 36 | information about its using clients. This information, for instance, is 37 | useful to identify potential review partner for API changes. 38 | 39 | Hint: A preferred way of client detection implementation is by logging 40 | of the client-id retrieved from the OAuth token. 41 | -------------------------------------------------------------------------------- /chapters/best-practices.adoc: -------------------------------------------------------------------------------- 1 | [[appendix-best-practices]] 2 | [appendix] 3 | = Best practices 4 | 5 | The best practices presented in this section are not part of the actual 6 | guidelines, but should provide guidance for common challenges we face when 7 | implementing RESTful APIs. 8 | 9 | 10 | [[cursor-based-pagination]] 11 | == Cursor-based pagination in RESTful APIs 12 | 13 | Cursor-based pagination is a very powerful and valuable technique (see also 14 | <<160>>) that allows to efficiently provide a stable view on changing data. 15 | This is obtained by using an anchor element that allows to retrieve all page 16 | elements directly via an ordering combined-index, usually based on `created_at` 17 | or `modified_at`. Simple said, the cursor is the information set needed to 18 | reconstruct the database query that retrieves the minimal page information from 19 | the data storage. 20 | 21 | The {cursor} itself is an opaque string, transmitted forth and back between 22 | service and clients, that must never be *inspected* or *constructed* by 23 | clients. Therefore, it is good practice to encode (encrypt) its content in a 24 | non-human-readable form. 25 | 26 | The {cursor} content usually consists of a pointer to the anchor element 27 | defining the page position in the collection, a flag whether the element is 28 | included or excluded into/from the page, the retrieval direction, and a hash 29 | over the applied query filters (or the query filter itself) to safely re-create 30 | the collection. It is important to note, that a {cursor} should be always 31 | defined in relation to the current page to anticipate all occurring changes 32 | when progressing. 33 | 34 | The {cursor} is usually defined as an encoding of the following information: 35 | 36 | [source,yaml] 37 | ---- 38 | Cursor: 39 | descriptions: > 40 | Cursor structure that contains all necessary information to efficiently 41 | retrieve a page from the data store. 42 | type: object 43 | properties: 44 | position: 45 | description: > 46 | Object containing the keys pointing to the anchor element that is 47 | defining the collection resource page. Normally the position is given 48 | by the first or the last page element. The position object contains all 49 | values required to access the element efficiently via the ordered, 50 | combined index, e.g `modified_at`, `id`. 51 | type: object 52 | properties: ... 53 | 54 | element: 55 | description: > 56 | Flag whether the anchor element, which is pointed to by the `position`, 57 | should be *included* or *excluded* from the result set. Normally, only 58 | the current page includes the pointed to element, while all others are 59 | exclude it. 60 | type: string 61 | enum: [ INCLUDED, EXCLUDED ] 62 | 63 | direction: 64 | description: > 65 | Flag for the retrieval direction that is defining which elements to 66 | choose from the collection resource starting from the anchor elements 67 | position. It is either *ascending* or *descending* based on the 68 | ordering combined index. 69 | type: string 70 | enum: [ ASCENDING, DESCENDING ] 71 | 72 | query_hash: 73 | description: > 74 | Stable hash calculated over all query filters applied to create the 75 | collection resource that is represented by this cursor. 76 | type: string 77 | 78 | query: 79 | description: > 80 | Object containing all query filters applied to create the collection 81 | resource that is represented by this cursor. 82 | type: object 83 | properties: ... 84 | 85 | required: 86 | - position 87 | - element 88 | - direction 89 | ---- 90 | 91 | *Note:* In case of complex and long search requests, e.g. when {GET-with-body} 92 | is already required, the {cursor} may not be able to include the `query` because 93 | of common HTTP parameter size restrictions. In this case the `query` filters 94 | should be transported via body - in the request as well as in the response, 95 | while the pagination consistency should be ensured via the `query_hash`. 96 | 97 | *Remark:* It is also important to check the efficiency of the data-access. 98 | You need to make sure that you have a fully ordered stable index, that allows 99 | to efficiently resolve all elements of a page. If necessary, you need to 100 | provide a combined index that includes the `id` to ensure the full order and 101 | additional filter criteria to ensure efficiency. 102 | 103 | === Further reading 104 | 105 | * https://dev.twitter.com/rest/public/timelines[Twitter] 106 | * http://use-the-index-luke.com/no-offset[Use the Index, Luke] 107 | * https://www.citusdata.com/blog/1872-joe-nelson/409-five-ways-paginate-postgres-basic-exotic[Paging 108 | in PostgreSQL] 109 | 110 | 111 | [[optimistic-locking]] 112 | == Optimistic locking in RESTful APIs 113 | 114 | === Introduction 115 | Optimistic locking might be used to avoid concurrent writes on the same entity, 116 | which might cause data loss. A client always has to retrieve a copy of an 117 | entity first and specifically update this one. If another version has been 118 | created in the meantime, the update should fail. In order to make this work, 119 | the client has to provide some kind of version reference, which is checked by 120 | the service, before the update is executed. Please read the more detailed 121 | description on how to update resources via {PUT} in the <>. 123 | 124 | A RESTful API usually includes some kind of search endpoint, which will then 125 | return a list of result entities. There are several ways to implement 126 | optimistic locking in combination with search endpoints which, depending on the 127 | approach chosen, might lead to performing additional requests to get the 128 | current version of the entity that should be updated. 129 | 130 | === `ETag` with `If-Match` header 131 | An {ETag} can only be obtained by performing a {GET} request on the single 132 | entity resource before the update, i.e. when using a search endpoint an 133 | additional request is necessary. 134 | 135 | Example: 136 | [source,http] 137 | ---- 138 | < GET /orders 139 | 140 | > HTTP/1.1 200 OK 141 | > { 142 | > "items": [ 143 | > { "id": "O0000042" }, 144 | > { "id": "O0000043" } 145 | > ] 146 | > } 147 | 148 | < GET /orders/BO0000042 149 | 150 | > HTTP/1.1 200 OK 151 | > ETag: osjnfkjbnkq3jlnksjnvkjlsbf 152 | > { "id": "BO0000042", ... } 153 | 154 | < PUT /orders/O0000042 155 | < If-Match: osjnfkjbnkq3jlnksjnvkjlsbf 156 | < { "id": "O0000042", ... } 157 | 158 | > HTTP/1.1 204 No Content 159 | ---- 160 | 161 | Or, if there was an update since the {GET} and the entity's {ETag} has changed: 162 | 163 | [source,http] 164 | ---- 165 | > HTTP/1.1 412 Precondition failed 166 | ---- 167 | 168 | ==== Pros 169 | * RESTful solution 170 | 171 | ==== Cons 172 | * Many additional requests are necessary to build a meaningful front-end 173 | 174 | [[etag-in-result-entities]] 175 | === `ETags` in result entities 176 | The ETag for every entity is returned as an additional property of that entity. 177 | In a response containing multiple entities, every entity will then have a 178 | distinct {ETag} that can be used in subsequent {PUT} requests. 179 | 180 | In this solution, the {e_tag} property should be `readonly` and never be expected 181 | in the {PUT} request payload. 182 | 183 | Example: 184 | [source,http] 185 | ---- 186 | < GET /orders 187 | 188 | > HTTP/1.1 200 OK 189 | > { 190 | > "items": [ 191 | > { "id": "O0000042", "etag": "osjnfkjbnkq3jlnksjnvkjlsbf", "foo": 42, "bar": true }, 192 | > { "id": "O0000043", "etag": "kjshdfknjqlowjdsljdnfkjbkn", "foo": 24, "bar": false } 193 | > ] 194 | > } 195 | 196 | < PUT /orders/O0000042 197 | < If-Match: osjnfkjbnkq3jlnksjnvkjlsbf 198 | < { "id": "O0000042", "foo": 43, "bar": true } 199 | 200 | > HTTP/1.1 204 No Content 201 | ---- 202 | 203 | Or, if there was an update since the {GET} and the entity's {ETag} has changed: 204 | 205 | [source,http] 206 | ---- 207 | > HTTP/1.1 412 Precondition failed 208 | ---- 209 | 210 | ==== Pros 211 | * Perfect optimistic locking 212 | 213 | ==== Cons 214 | * Information that only belongs in the HTTP header is part of the business 215 | objects 216 | 217 | === Version numbers 218 | The entities contain a property with a version number. When an update is 219 | performed, this version number is given back to the service as part of the 220 | payload. The service performs a check on that version number to make sure it 221 | was not incremented since the consumer got the resource and performs the 222 | update, incrementing the version number. 223 | 224 | Since this operation implies a modification of the resource by the service, a 225 | {POST} operation on the exact resource (e.g. `POST /orders/O0000042`) should be 226 | used instead of a {PUT}. 227 | 228 | In this solution, the `version` property is not `readonly` since it is provided 229 | at {POST} time as part of the payload. 230 | 231 | Example: 232 | [source,http] 233 | ---- 234 | < GET /orders 235 | 236 | > HTTP/1.1 200 OK 237 | > { 238 | > "items": [ 239 | > { "id": "O0000042", "version": 1, "foo": 42, "bar": true }, 240 | > { "id": "O0000043", "version": 42, "foo": 24, "bar": false } 241 | > ] 242 | > } 243 | 244 | < POST /orders/O0000042 245 | < { "id": "O0000042", "version": 1, "foo": 43, "bar": true } 246 | 247 | > HTTP/1.1 204 No Content 248 | ---- 249 | 250 | or if there was an update since the {GET} and the version number in the 251 | database is higher than the one given in the request body: 252 | 253 | [source,http] 254 | ---- 255 | > HTTP/1.1 409 Conflict 256 | ---- 257 | 258 | ==== Pros 259 | * Perfect optimistic locking 260 | 261 | ==== Cons 262 | * Functionality that belongs into the HTTP header becomes part of the 263 | business object 264 | * Using {POST} instead of PUT for an update logic (not a problem in itself, 265 | but may feel unusual for the consumer) 266 | 267 | === `Last-Modified` / `If-Unmodified-Since` 268 | In HTTP 1.0 there was no {ETag} and the mechanism used for optimistic locking 269 | was based on a date. This is still part of the HTTP protocol and can be used. 270 | Every response contains a {Last-Modified} header with a HTTP date. When 271 | requesting an update using a {PUT} request, the client has to provide this 272 | value via the header {If-Unmodified-Since}. The server rejects the request, if 273 | the last modified date of the entity is after the given date in the header. 274 | 275 | This effectively catches any situations where a change that happened between 276 | {GET} and {PUT} would be overwritten. In the case of multiple result entities, 277 | the {Last-Modified} header will be set to the latest date of all the entities. 278 | This ensures that any change to any of the entities that happens between {GET} 279 | and {PUT} will be detectable, without locking the rest of the batch as well. 280 | 281 | Example: 282 | [source,http] 283 | ---- 284 | < GET /orders 285 | 286 | > HTTP/1.1 200 OK 287 | > Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT 288 | > { 289 | > "items": [ 290 | > { "id": "O0000042", ... }, 291 | > { "id": "O0000043", ... } 292 | > ] 293 | > } 294 | 295 | < PUT /block/O0000042 296 | < If-Unmodified-Since: Wed, 22 Jul 2009 19:15:56 GMT 297 | < { "id": "O0000042", ... } 298 | 299 | > HTTP/1.1 204 No Content 300 | ---- 301 | 302 | Or, if there was an update since the {GET} and the entities last modified is 303 | later than the given date: 304 | 305 | [source,http] 306 | ---- 307 | > HTTP/1.1 412 Precondition failed 308 | ---- 309 | 310 | ==== Pros 311 | * Well established approach that has been working for a long time 312 | * No interference with the business objects; the locking is done via HTTP 313 | headers only 314 | * Very easy to implement 315 | * No additional request needed when updating an entity of a search endpoint 316 | result 317 | 318 | ==== Cons 319 | * If a client communicates with two different instances and their clocks are 320 | not perfectly in sync, the locking could potentially fail 321 | 322 | === Conclusion 323 | We suggest to either use the _{ETag} in result entities_ or _{Last-Modified} 324 | / {If-Unmodified-Since}_ approach. 325 | 326 | 327 | [[handling-compatible-extensions]] 328 | == Handling compatible API extensions 329 | 330 | [[client-handling-compatible-extensions]] 331 | === In the clients 332 | 333 | Client must not eliminate unknown optional fields from the 334 | fetched resource payload, and to serialize them later when submitting the 335 | complete resource payload back to the API server via {PUT} (<<108>>). 336 | 337 | When using Java with Jackson serialization, for example, that can be achieved 338 | by including a field in the Java class representing the API resource, like the 339 | following one: 340 | 341 | [source,java] 342 | ---- 343 | @JsonAnyGetter 344 | @JsonAnySetter 345 | private Map additionalProperties = new HashMap<>(); 346 | ---- 347 | -------------------------------------------------------------------------------- /chapters/changelog.adoc: -------------------------------------------------------------------------------- 1 | [[appendix-changelog]] 2 | [appendix] 3 | = Changelog 4 | 5 | This change log only contains major changes and lists major changes since October 2016. 6 | 7 | Non-major changes are editorial-only changes or minor changes of existing guidelines, e.g. adding new error code or specific example. 8 | Major changes are changes that come with additional obligations, or even change an existing guideline obligation. 9 | Major changes are listed as "Rule Changes" below. 10 | 11 | *Hint:* Most recent major changes might be missing in the list since we update it 12 | only occasionally, not with each pull request, to avoid merge commits. 13 | Please have a look at the 14 | https://github.com/zalando/restful-api-guidelines/commits/main[commit list in Github] 15 | to see a list of all changes. 16 | 17 | [[rule-changes]] 18 | == Rule Changes 19 | 20 | * `2024-06-27`: Clarified usage of {x-extensible-enum} for events in <<112>>. ^https://github.com/zalando/restful-api-guidelines/pull/807[#807]^ 21 | * `2024-06-25`: Relaxed naming convention for date/time properties in <<235>>. ^https://github.com/zalando/restful-api-guidelines/pull/811[#811]^ 22 | * `2024-06-11`: Linked <<127>> (duration / period) from <<238>>. ^https://github.com/zalando/restful-api-guidelines/pull/810[#810]^ 23 | * `2024-05-06`: Added new rule <<255>> on selecting appropriate date or date-time format. ^https://github.com/zalando/restful-api-guidelines/pull/808[#808]^ 24 | * `2024-04-16`: Removed sort example from simple query language in <<236>>. Enhanced clarity for 'uid' usage and permission naming convention in <<105>> and <<225>>. ^https://github.com/zalando/restful-api-guidelines/pull/804[#804]^ ^https://github.com/zalando/restful-api-guidelines/pull/801[#801]^ 25 | * `2024-03-21`: Added best practices section for <<108>> on handling compatible API extensions. ^https://github.com/zalando/restful-api-guidelines/pull/799[#799]^ 26 | * `2024-03-05`: Updated security section about uid scope in <<105>>. ^https://github.com/zalando/restful-api-guidelines/pull/794[#794]^ 27 | * `2024-02-29`: Improved guidance on {POST} usage in <<148>>. ^https://github.com/zalando/restful-api-guidelines/pull/791[#791]^ 28 | * `2024-02-21`: Fixed discrepancy between <<109>> and <<111>> regarding handling of unexpected fields. ^https://github.com/zalando/restful-api-guidelines/pull/793[#793]^ 29 | * `2023-12-12`: Improved response code guidance in <<150>>. ^https://github.com/zalando/restful-api-guidelines/pull/789[#789]^ 30 | * `2023-11-22`: Added new rule <<253>> for supporting asynchronous request processing. ^https://github.com/zalando/restful-api-guidelines/pull/787[#787]^ 31 | * `2023-07-21`: Improved guidance on total counts in <<254>>. ^https://github.com/zalando/restful-api-guidelines/pull/731[#731]^ 32 | * `2023-05-12`: Added new rule <<250>> recommending not to use redirection codes. ^https://github.com/zalando/restful-api-guidelines/pull/762[#762]^ 33 | * `2023-05-08`: Improved guidance on sanitizing JSON payload in <<250>>. ^https://github.com/zalando/restful-api-guidelines/pull/759[#759]^ 34 | * `2023-04-18`: Added new rule <<252>> recommending to design single resource schema for reading and writing. Added exception for partner IAM to <<104>>. ^https://github.com/zalando/restful-api-guidelines/pull/764[#764]^ ^https://github.com/zalando/restful-api-guidelines/pull/767[#767]^ 35 | * `2022-12-20`: Clarify that event consumers must be robust against duplicates in <<214>>. ^https://github.com/zalando/restful-api-guidelines/pull/749[#749]^ 36 | * `2022-10-18`: Add X-Zalando-Customer to list of proprietary headers in <<183>>. ^https://github.com/zalando/restful-api-guidelines/pull/743[#743]^ 37 | * `2022-09-21`: Clarify that functional naming schema in <<223>> is a {MUST}/{SHOULD}/{MAY} rule. ^https://github.com/zalando/restful-api-guidelines/pull/740[#740]^ 38 | * `2022-07-26`: Improve guidance for return code usage for (robust) create operations in <<148>>. ^https://github.com/zalando/restful-api-guidelines/pull/735[#735]^ 39 | * `2022-07-21`: Improve format and time interval guidance in <<238>> and <<127>>. ^https://github.com/zalando/restful-api-guidelines/pull/733[#733]^ 40 | * `2022-05-24`: Define `next` page link as optional in <>. ^https://github.com/zalando/restful-api-guidelines/pull/726[#726]^ 41 | * `2022-04-19`: Change <<200>> from {MUST} to {SHOULD} avoid providing sensitive data with events. ^https://github.com/zalando/restful-api-guidelines/pull/723[#723]^ 42 | * `2022-03-22`: More clarity about when error specification definitions can be omitted in <<151>>. More clarity around HTTP status codes in <<243>>. More clarity for avoiding null for boolean fields in <<122>>. ^https://github.com/zalando/restful-api-guidelines/pull/715[#715]^ ^https://github.com/zalando/restful-api-guidelines/pull/720[#720]^ ^https://github.com/zalando/restful-api-guidelines/pull/721[#721]^ 43 | * `2022-01-26`: Exclude 'type' from common field names in <<235>>. Improve clarity around usage of extensible enums in <<112>>. ^https://github.com/zalando/restful-api-guidelines/pull/714[#714]^ ^https://github.com/zalando/restful-api-guidelines/pull/717[#717]^ 44 | * `2021-12-22`: Clarify that event id must not change in retry situations when producers <<211>>. ^https://github.com/zalando/restful-api-guidelines/pull/694[#694]^ 45 | * `2021-12-09`: Improve clarity on {PATCH} and {PUT} usage in rule <<148>>. Only use codes registered via IANA in rule <<243>>. ^https://github.com/zalando/restful-api-guidelines/commit/cb0624b2bc128b32fba0f78dd24f1a67d5e62766[cb0624b]^ 46 | * `2021-12-09`: event id must not change in retry situations when producers <<211>>. 47 | * `2021-11-24`: restructuring of the document and some rules. 48 | * `2021-10-18`: new rule <<244>>. 49 | * `2021-10-12`: improve clarity on {PATCH} usage in rule <<148>>. 50 | * `2021-08-24`: improve clarity on {PUT} usage in rule <<148>>. 51 | * `2021-08-24`: only use codes registered via IANA in rule <<243>>. 52 | * `2021-08-17`: update formats per OpenAPI 3.1 in <<238>>. 53 | * `2021-06-22`: <<238>> changed from {SHOULD} to {MUST}; consistency for rules around standards for data. 54 | * `2021-06-03`: <<104>> with clear distinction of OpenAPI security schemes, favoring `bearer` to `oauth2`. 55 | * `2021-06-01`: resolve uncertainties around 'occurred_at' semantics of <>. 56 | * `2021-05-25`: <<172>> with <<114, API endpoint versioning>> as only custom media type usage exception. 57 | * `2021-05-05`: define usage on resource-ids in {PUT} and {POST} in <<148>>. 58 | * `2021-04-29`: improve clarity of <<133>>. 59 | * `2021-03-19`: clarity on <<167>>. 60 | * `2021-03-15`: <<242>> changed from {SHOULD} to {MUST}; improve clarity around <<203, event ordering>>. 61 | * `2021-03-19`: best practice section <> 62 | * `2021-02-16`: define how to reference models outside the api in <<234>>. 63 | * `2021-02-15`: improve guideline <<176>> -- clients must be prepared to not receive problem return objects. 64 | * `2021-01-19`: more details for {GET-with-Body} and {DELETE-with-Body} (<<148>>). 65 | * `2020-09-29`: include models for headers to be included by reference in API definitions (<<183>>) 66 | * `2020-09-08`: add exception for legacy host names to <<224>> 67 | * `2020-08-25`: change <<240>> from {MUST} to {SHOULD}, explain exceptions 68 | * `2020-08-25`: add exception for `self` to <<143>> and <<134>>. 69 | * `2020-08-24`: change "{MUST} avoid trailing slashes" to <<136>>. 70 | * `2020-08-20`: change <<183>> from {MUST} to {SHOULD}, mention gateway-specific headers (which are not part of the public API). 71 | * `2020-06-30`: add details to <<114>> 72 | * `2020-05-19`: new sections about DELETE with query parameters and {DELETE-with-Body} in <<148>>. 73 | * `2020-02-06`: new rule <<241>> 74 | * `2020-02-05`: add Sunset header, clarify deprecation producedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>) 75 | * `2020-01-21`: new rule <<240>> (as MUST, changed later to SHOULD) 76 | * `2020-01-15`: change "Warning" to "Deprecation" header in <<189>>, <<190>>. 77 | * `2019-10-10`: remove never-implemented rule "{MUST} Permissions on events must correspond to API permissions" 78 | * `2019-09-10`: remove duplicated rule "{MAY} Standards could be used for Language, Country and Currency", upgrade <<170>> from {MAY} to {SHOULD}. 79 | * `2019-08-29`: new rule <<239>>, extend <<167>> pointing to {RFC-7493}[RFC-7493] 80 | * `2019-08-29`: new rules <<236>>, <<237>> 81 | * `2019-07-30`: new rule <<238>> 82 | * `2019-07-30`: change <<173>> from {SHOULD} to {MUST} 83 | * `2019-07-30`: change "{SHOULD} Null values should have their fields removed to" <<123>>. 84 | * `2019-07-25`: new rule <<235>>. 85 | * `2019-07-18`: improved cursor guideline for {GET-with-Body}. 86 | * `2019-06-25`: change <<154>> from {SHOULD} to {MUST}, use OpenAPI 3 syntax 87 | * `2019-06-13`: remove `X-App-Domain` from <<183>>. 88 | * `2019-05-17`: add `X-Mobile-Advertising-Id` to <<183>>. 89 | * `2019-04-09` New rule <<234>> 90 | * `2019-02-19`: New rule <<233>> extracted + expanded from <<183>>. 91 | * `2019-01-24:` Improve guidance on caching (<<149>>, <<227>>). 92 | * `2019-01-21:` Improve guidance on idempotency, introduce idempotency-key (<<229>>, <<231>>). 93 | * `2019-01-16`: Change <<135>> from {MAY} to {SHOULD NOT} 94 | * `2018-10-19`: Add `ordering_key_field` to event type definition schema (<<197>>, <<203>>) 95 | * `2018-09-28`: New rule <<228>> 96 | * `2018-09-13`: replaced OpenAPI 2.0 syntax with OpenAPI 3.0 in the example snippets 97 | * `2018-08-10`: New rule <<226>> 98 | * `2018-07-12`: Add `audience` field to event type definition (<<197>>) 99 | * `2018-06-11:` Introduced new naming guidelines for host, permission, and event names. 100 | * `2018-01-10:` Moved meta information related aspects into new chapter <>. 101 | * `2018-01-09:` Changed publication requirements for API specifications (<<192>>). 102 | * `2017-12-07:` Added best practices section including discussion about optimistic locking approaches. 103 | * `2017-11-28:` Changed OAuth flow example from password to client credentials in <>. 104 | * `2017-11-22:` Updated description of X-Tenant-ID header field 105 | * `2017-08-22:` Migration to Asciidoc 106 | * `2017-07-20:` Be more precise on client vs. server obligations for compatible API extensions. 107 | * `2017-06-06:` Made money object guideline clearer. 108 | * `2017-05-17:` Added guideline on query parameter collection format. 109 | * `2017-05-10:` Added the convention of using RFC2119 to describe guideline levels, and replaced `book.could` with `book.may`. 110 | * `2017-03-30:` Added rule that permissions on resources in events must correspond to permissions on API resources 111 | * `2017-03-30:` Added rule that APIs should be modelled around business processes 112 | * `2017-02-28:` Extended information about how to reference sub-resources and the usage of composite identifiers in the <<143>> 113 | part. 114 | * `2017-02-22:` Added guidance for conditional requests with If-Match/If-None-Match 115 | * `2017-02-02:` Added guideline for batch and bulk request 116 | * `2017-02-01:` <<180>> 117 | * `2017-01-18:` Removed "Avoid Javascript Keywords" rule 118 | * `2017-01-05:` Clarification on the usage of the term "REST/RESTful" 119 | * `2016-12-07:` Introduced "API as a Product" principle 120 | * `2016-12-06:` New guideline: "Should Only Use UUIDs If Necessary" 121 | * `2016-12-04:` Changed OAuth flow example from implicit to password in <>. 122 | * `2016-10-13:` <<172>> 123 | * `2016-10-10:` Introduced the changelog. From now on all rule changes on API guidelines will be recorded here. 124 | -------------------------------------------------------------------------------- /chapters/compatibility.adoc: -------------------------------------------------------------------------------- 1 | [[compatibility]] 2 | = REST Design - Compatibility 3 | 4 | 5 | [#106] 6 | == {MUST} not break backward compatibility 7 | 8 | Change APIs, but keep all consumers running. Consumers usually have independent 9 | release lifecycles, focus on stability, and avoid changes that do not provide 10 | additional value. APIs are contracts between service providers and service 11 | consumers that cannot be broken via unilateral decisions. 12 | 13 | There are two techniques to change APIs without breaking them: 14 | 15 | * follow rules for compatible extensions 16 | * introduce new API versions and still support older versions with 17 | https://opensource.zalando.com/restful-api-guidelines/#deprecation[deprecation] 18 | 19 | We strongly encourage using compatible API extensions and discourage versioning 20 | (see <<113>> and <<114>> below). The following guidelines for service providers 21 | (<<107>>) and consumers (<<108>>) enable us (having Postel’s Law in mind) to 22 | make compatible changes without versioning. 23 | 24 | *Note:* There is a difference between incompatible and breaking changes. 25 | Incompatible changes are changes that are not covered by the compatibility 26 | rules below. Breaking changes are incompatible changes deployed into operation, 27 | and thereby breaking running API consumers. Usually, incompatible changes are 28 | breaking changes when deployed into operation. However, in specific controlled 29 | situations it is possible to deploy incompatible changes in a non-breaking way, 30 | if no API consumer is using the affected API aspects (see also <> guidelines). 32 | 33 | *Hint:* Please note that the compatibility guarantees are for the "on the wire" 34 | format. Binary or source compatibility of code generated from an API definition 35 | is not covered by these rules. If client implementations update their 36 | generation process to a new version of the API definition, it has to be 37 | expected that code changes are necessary. 38 | 39 | 40 | [#107] 41 | == {SHOULD} prefer compatible extensions 42 | 43 | API designers should apply the following rules to evolve RESTful APIs for 44 | services in a backward-compatible way: 45 | 46 | * Add only optional, never mandatory fields. 47 | * Never change the semantic of fields (e.g. changing the semantic from 48 | customer-number to customer-id, as both are different unique customer keys) 49 | * Input fields may have (complex) constraints being validated via server-side 50 | business logic. Never change the validation logic to be more restrictive and 51 | make sure that all constraints are clearly defined in description. 52 | * `enum` ranges can be reduced when used as input parameters, only if the server 53 | is ready to accept and handle old range values too. The range can be reduced 54 | when used as output parameters. 55 | * `enum` ranges cannot be extended when used for output parameters — clients may 56 | not be prepared to handle it. However, enum ranges can be extended when used 57 | for input parameters. 58 | * You <<112>> that are used for output parameters and likely to 59 | be extended with growing functionality. The API definition should be updated 60 | first before returning new values. 61 | * Consider <<250>> in case a URL has to change. 62 | 63 | 64 | [#109] 65 | == {SHOULD} design APIs conservatively 66 | 67 | Designers of service provider APIs should be conservative and accurate in what 68 | they accept from clients: 69 | 70 | * Unknown input fields in payload or URL should not be ignored; servers should 71 | provide error feedback to clients via an HTTP 400 response code. 72 | Otherwise, if unexpected fields are planned to be handled in some way instead 73 | of being rejected, API designers *must* document clearly how unexpected 74 | fields are being handled for {PUT}, {POST}, and {PATCH} requests. 75 | * Be accurate in defining input data constraints (like formats, ranges, lengths 76 | etc.) — and check constraints and return dedicated error information in case 77 | of violations. 78 | * Prefer being more specific and restrictive (if compliant to functional 79 | requirements), e.g. by defining length range of strings. It may simplify 80 | implementation while providing freedom for further evolution as compatible 81 | extensions. 82 | 83 | Not ignoring unknown input fields is a specific deviation from Postel's Law 84 | (e.g. see also + 85 | https://cacm.acm.org/magazines/2011/8/114933-the-robustness-principle-reconsidered/fulltext[The 86 | Robustness Principle Reconsidered]) and a strong recommendation. Servers might 87 | want to take different approach but should be aware of the following problems 88 | and be explicit in what is supported: 89 | 90 | * Ignoring unknown input fields is actually not an option for {PUT}, since it 91 | becomes asymmetric with subsequent {GET} response and HTTP is clear about the 92 | {PUT} _replace_ semantics and default roundtrip expectations (see 93 | {RFC-9110}#section-9.3.4[RFC 9110 Section 9.3.4]). Note, accepting (i.e. not 94 | ignoring) unknown input fields and returning it in subsequent {GET} responses 95 | is a different situation and compliant to {PUT} semantics. 96 | * Certain client errors cannot be recognized by servers, e.g. attribute name 97 | typing errors will be ignored without server error feedback. The server 98 | cannot differentiate between the client intentionally providing an additional 99 | field versus the client sending a mistakenly named field, when the client's 100 | actual intent was to provide an optional input field. 101 | * Future extensions of the input data structure might be in conflict with 102 | already ignored fields and, hence, will not be compatible, i.e. break clients 103 | that already use this field but with different type. 104 | 105 | In specific situations, where a (known) input field is not needed anymore, it 106 | either can stay in the API definition with "not used anymore" description or 107 | can be removed from the API definition as long as the server ignores this 108 | specific parameter. 109 | 110 | 111 | [#108] 112 | == {MUST} prepare clients to accept compatible API extensions 113 | 114 | Service clients should apply the robustness principle: 115 | 116 | * Be conservative with API requests and data passed as input, e.g. avoid to 117 | exploit definition deficits like passing megabytes of strings with 118 | unspecified maximum length. 119 | * Be tolerant in processing and reading data of API responses, more 120 | specifically service clients must be prepared for compatible API extensions 121 | of response data: 122 | 123 | ** Be tolerant with unknown fields in the payload (see also Fowler’s 124 | http://martinfowler.com/bliki/TolerantReader.html["TolerantReader"] post), 125 | i.e. ignore new fields but do not eliminate them from payload if needed for 126 | subsequent {PUT} requests. 127 | ** Be prepared that {x-extensible-enum} return parameters (see <<112, rule 112>>) may deliver new values; 128 | either be agnostic or provide default behavior for unknown values, and do not eliminate them. 129 | ** Be prepared to handle HTTP status codes not explicitly specified in endpoint 130 | definitions. Note also, that status codes are extensible. Default handling is 131 | how you would treat the corresponding {x00} code (see 132 | {RFC-9110}#section-15[RFC 9110 Section 15]). 133 | ** Follow the redirect when the server returns HTTP status code {301} (Moved 134 | Permanently). 135 | 136 | The <> section describes a best practice to implement the requirements in Java. 137 | 138 | [#111] 139 | == {MUST} treat OpenAPI specification as open for extension by default 140 | 141 | The OpenAPI specification is not very specific on default extensibility 142 | of objects, and redefines JSON-Schema keywords related to extensibility, like 143 | `additionalProperties`. Following our compatibility guidelines, OpenAPI 144 | object definitions are considered open for extension by default as per 145 | http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.18[Section 146 | 5.18 "additionalProperties"] of JSON-Schema. 147 | 148 | When it comes to OpenAPI, this means an `additionalProperties` declaration 149 | is not required to make an object definition extensible: 150 | 151 | * API clients consuming data must not assume that objects are closed for 152 | extension in the absence of an `additionalProperties` declaration and must 153 | ignore fields sent by the server they cannot process. This allows API 154 | servers to evolve their data formats. 155 | * For API servers receiving unexpected data, the situation is slightly 156 | different. According to <<109>>, instead of ignoring fields, 157 | servers _should_ reject requests whose entities contain undefined fields 158 | in order to signal to clients that those fields would not be stored on behalf 159 | of the client. 160 | Otherwise, if unexpected fields are planned to be handled in some way instead 161 | of being rejected, API designers *must* document clearly how unexpected 162 | fields are being handled for {PUT}, {POST}, and {PATCH} requests. 163 | 164 | API formats must not declare `additionalProperties` to be false, as this 165 | prevents objects being extended in the future. 166 | 167 | Note that this guideline concentrates on default extensibility and does not 168 | exclude the use of `additionalProperties` with a schema as a value, which might 169 | be appropriate in some circumstances, e.g. see <<216>>. 170 | 171 | 172 | [#113] 173 | == {SHOULD} avoid versioning 174 | 175 | When changing your RESTful APIs, do so in a compatible way and avoid generating 176 | additional API versions. Multiple versions can significantly complicate 177 | understanding, testing, maintaining, evolving, operating and releasing our 178 | systems 179 | (http://martinfowler.com/articles/enterpriseREST.html[supplementary 180 | reading]). 181 | 182 | If changing an API can’t be done in a compatible way, then proceed in one of 183 | these three ways: 184 | 185 | * create a new resource (variant) in addition to the old resource variant 186 | * create a new service endpoint — i.e. a new application with a new API (with a 187 | new domain name) 188 | * create a new API version supported in parallel with the old API by the same 189 | microservice 190 | 191 | As we discourage versioning by all means because of the manifold disadvantages, 192 | we strongly recommend to only use the first two approaches. 193 | 194 | 195 | [#114] 196 | == {MUST} use media type versioning 197 | 198 | However, when API versioning is unavoidable, you have to design your 199 | multi-version RESTful APIs using media type versioning (see <<115>>). 200 | Media type versioning is less tightly coupled since 201 | it supports content negotiation and hence reduces complexity of release 202 | management. 203 | 204 | Version information and media type are provided 205 | together via the HTTP `Content-Type` header — e.g. 206 | `application/x.zalando.cart+json;version=2`. For incompatible changes, a new 207 | media type version for the resource is created. To generate the new 208 | representation version, consumer and producer can do <<244, content negotiation>> using 209 | the HTTP `Content-Type` and `Accept` headers. 210 | 211 | NOTE: This versioning method only applies to 212 | the request and response payload schema, not to URI or method semantics. 213 | 214 | === Custom media type format 215 | 216 | Custom media type format should have the following pattern: 217 | 218 | [source,http] 219 | ---- 220 | application/x.+json;version= 221 | ---- 222 | 223 | * `` is a custom type name, e.g. `x.zalando.cart` 224 | * `` is a (sequence) number, e.g. `2` 225 | 226 | === Example 227 | 228 | In this example, a client wants only the new version of the response: 229 | 230 | [source,http] 231 | ---- 232 | Accept: application/x.zalando.cart+json;version=2 233 | ---- 234 | 235 | A server responding to this, as well as a client sending a request with content 236 | should use the `Content-Type` header, declaring that one is sending the new 237 | version: 238 | 239 | [source,http] 240 | ---- 241 | Content-Type: application/x.zalando.cart+json;version=2 242 | ---- 243 | 244 | Media type versioning should... 245 | 246 | * Use a custom media type, e.g. `application/x.zalando.cart+json` 247 | * Include media type versions in request and response headers to increase visibility 248 | * Include `Content-Type` in the `Vary` header to enable proxy caches to differ 249 | between versions 250 | 251 | [source,http] 252 | ---- 253 | Vary: Content-Type 254 | ---- 255 | 256 | NOTE: Until an incompatible change is necessary, it is recommended to stay 257 | with the standard `application/json` media type without versioning. 258 | 259 | Further reading: 260 | https://apisyouwonthate.com/blog/api-versioning-has-no-right-way[API 261 | Versioning Has No "Right Way"] provides an overview on different versioning 262 | approaches to handle breaking changes without being opinionated. 263 | 264 | 265 | [#115] 266 | == {MUST} not use URL versioning 267 | 268 | With URL versioning a (major) version number is included in the path, e.g. 269 | `/v1/customers`. The consumer has to wait until the provider has been released 270 | and deployed. If the consumer also supports hypermedia links — even in their 271 | APIs — to drive workflows (HATEOAS), this quickly becomes complex. So does 272 | coordinating version upgrades — especially with hyperlinked service 273 | dependencies — when using URL versioning. To avoid this tighter coupling and 274 | complexer release management we do not use URL versioning, instead we <<114>> 275 | with content negotiation. 276 | 277 | 278 | [#110] 279 | == {MUST} always return JSON objects as top-level data structures 280 | 281 | In a response body, you must always return a JSON object (and not e.g. an 282 | array) as a top level data structure to support future extensibility. JSON 283 | objects support compatible extension by additional attributes. This allows you 284 | to easily extend your response and e.g. add pagination later, without breaking 285 | backwards compatibility. See <<161>> for an example. 286 | 287 | Maps (see <<216>>), even though technically objects, are also forbidden as top 288 | level data structures, since they don't support compatible, future extensions. 289 | 290 | 291 | [#112] 292 | == {SHOULD} use open-ended list of values (`x-extensible-enum`) for enumeration types 293 | 294 | JSON schema `enum` is per definition a closed set of values that is assumed to be 295 | complete and not intended for extension. This closed principle of enumerations 296 | imposes compatibility issues when an enumeration must be extended. To avoid 297 | these issues, we recommend to use an open-ended list of values instead 298 | of an enumeration unless: 299 | 300 | 1. the API has full control of the enumeration values, i.e. the list of values 301 | does not depend on any external tool or interface, and 302 | 2. the list of values is complete with respect to any thinkable and unthinkable 303 | future feature. 304 | 305 | To specify an open-ended list of values via the {x-extensible-enum} property as follows: 306 | 307 | [source,yaml] 308 | ---- 309 | delivery_methods: 310 | type: string 311 | x-extensible-enum: 312 | - PARCEL 313 | - LETTER 314 | - EMAIL 315 | ---- 316 | 317 | *Note:* {x-extensible-enum} is a proprietary extension of the JSON Schema standard that 318 | is e.g. visible via Swagger UI, but ignored by most other tools. 319 | 320 | See <<240>> about enum value naming conventions. 321 | 322 | Note, {x-extensible-enum} is a different concept than JSON schema `examples` which is 323 | just a list of a few example values, whereas {x-extensible-enum} defines all valid 324 | values (for a specific API and service version) and has the advantage of an extensible 325 | full type-range specification that is validated by the service. 326 | 327 | *Important:* Clients must be prepared for extensions of enums returned with server responses, i.e. 328 | must implement a fallback / default behavior to handle unknown new enum values -- see <<108>>. 329 | API owners must take care to extend enums in a compatible way that does not change the 330 | semantics of already existing enum values, for instance, do not split an old enum value 331 | into two new enum values. Services should only extend {x-extensible-enum} ranges, and only accept 332 | and return values listed in the API definition, i.e. the API definition needs to be updated first 333 | before the service accepts/returns new values -- see also <<107>>. 334 | 335 | -------------------------------------------------------------------------------- /chapters/data-formats.adoc: -------------------------------------------------------------------------------- 1 | [[data-formats]] 2 | = REST Basics - Data formats 3 | 4 | 5 | [#238] 6 | == {MUST} use standard data formats 7 | 8 | https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#data-types[Open API] 9 | (based on https://tools.ietf.org/html/draft-bhutton-json-schema-validation-00#section-7.3[JSON Schema Validation vocabulary]) 10 | defines formats from ISO and IETF standards for date/time, integers/numbers and binary data. 11 | You *must* use these formats, whenever applicable: 12 | 13 | [cols="10%,10%,40%,40%",options="header",] 14 | |===================================================================== 15 | | `OpenAPI type` | `OpenAPI format` | Specification | Example 16 | | `integer` | `int32` | 4 byte signed integer between pass:[-231] and pass:[231]-1 | `7721071004` 17 | | `integer` | `int64` | 8 byte signed integer between pass:[-263] and pass:[263]-1 | `772107100456824` 18 | | `integer` | `bigint` | arbitrarily large signed integer number | `77210710045682438959` 19 | | `number` | `float` | `binary32` single precision decimal number -- see {IEEE-754-2008}[IEEE 754-2008/ISO 60559:2011] | `3.1415927` 20 | | `number` | `double` | `binary64` double precision decimal number -- see {IEEE-754-2008}[IEEE 754-2008/ISO 60559:2011] | `3.141592653589793` 21 | | `number` | `decimal` | arbitrarily precise signed decimal number | `3.141592653589793238462643383279` 22 | | `string` | `byte` | `base64url` encoded byte following {RFC-7493}#section-4.4[RFC 7493 Section 4.4] | `"VA=="` 23 | | `string` | `binary` | `base64url` encoded byte sequence following {RFC-7493}#section-4.4[RFC 7493 Section 4.4] | `"VGVzdA=="` 24 | | `string` | `date` | {RFC-3339}[RFC 3339] internet profile -- subset of https://tools.ietf.org/html/rfc3339#ref-ISO8601[ISO 8601] | `"2019-07-30"` 25 | | `string` | `date-time` | {RFC-3339}[RFC 3339] internet profile -- subset of https://tools.ietf.org/html/rfc3339#ref-ISO8601[ISO 8601] |`"2019-07-30T06:43:40.252Z"` 26 | | `string` | `time` | {RFC-3339}[RFC 3339] internet profile -- subset of https://tools.ietf.org/html/rfc3339#ref-ISO8601[ISO 8601] | `"06:43:40.252Z"` 27 | | `string` | `duration` | {RFC-3339}[RFC 3339] -- subset of https://tools.ietf.org/html/rfc3339#ref-ISO8601[ISO 8601], see also <<#127,rule #127>> for details. | `"P1DT3H4S"` 28 | | `string` | `period` | {RFC-3339}[RFC 3339] -- subset of https://tools.ietf.org/html/rfc3339#ref-ISO8601[ISO 8601], see also <<#127,rule #127>> for details. | `"2022-06-30T14:52:44.276/PT48H" "PT24H/2023-07-30T18:22:16.315Z" "2024-05-15T09:48:56.317Z/.."` 29 | | `string` | `password` | | `"secret"` 30 | | `string` | `email` | {RFC-5322}[RFC 5322] | `"example@zalando.de"` 31 | | `string` | `idn-email` | {RFC-6531}[RFC 6531] | `"hello@bücher.example"` 32 | | `string` | `hostname` | {RFC-1034}[RFC 1034] | `"www.zalando.de"` 33 | | `string` | `idn-hostname` | {RFC-5890}[RFC 5890] | `"bücher.example"` 34 | | `string` | `ipv4` | {RFC-2673}[RFC 2673] | `"104.75.173.179"` 35 | | `string` | `ipv6` | {RFC-4291}[RFC 4291] | `"2600:1401:2::8a"` 36 | | `string` | `uri` | {RFC-3986}[RFC 3986] | `"https://www.zalando.de/"` 37 | | `string` | `uri-reference` | {RFC-3986}[RFC 3986] | `"/clothing/"` 38 | | `string` | `uri-template` | {RFC-6570}[RFC 6570] | `"/users/\{id\}"` 39 | | `string` | `iri` | {RFC-3987}[RFC 3987] | `"https://bücher.example/"` 40 | | `string` | `iri-reference` | {RFC-3987}[RFC 3987] | `"/damenbekleidung-jacken-mäntel/"` 41 | | `string` | <<144, `uuid`>> | {RFC-4122}[RFC 4122] | `"e2ab873e-b295-11e9-9c02-..."` 42 | | `string` | `json-pointer` | {RFC-6901}[RFC 6901] | `"/items/0/id"` 43 | | `string` | `relative-json-pointer` | https://tools.ietf.org/html/draft-handrews-relative-json-pointer[Relative JSON pointers] | `"1/id"` 44 | | `string` | `regex` | regular expressions as defined in {ECMA-262}[ECMA 262] | `"^[a-z0-9]+$"` 45 | |===================================================================== 46 | 47 | *Note:* Formats `bigint` and `decimal` have been added to the OpenAPI defined formats -- 48 | see also <<171>> and <<169>> below. 49 | 50 | We add further OpenAPI formats that are useful especially in an e-commerce environment 51 | e.g. `language code`, `country code`, and `currency` based other ISO and IETF standards. 52 | You *must* use these formats, whenever applicable: 53 | 54 | [cols="10%,10%,40%,40%",options="header",] 55 | |===================================================================== 56 | | `OpenAPI type` | `format` | Specification | Example 57 | | `string` | `iso-639-1`| two letter language code -- see {ISO-639-1}[ISO 639-1]. Hint: In the past we used `iso-639` as format. | `"en"` 58 | | `string` | `bcp47` | multi letter language tag -- see {BCP47}[BCP 47]. It is a compatible extension of {ISO-639-1}[ISO 639-1] optionally with additional information for language usage, like region, variant, script. | `"en-DE"` 59 | | `string` | `iso-3166-alpha-2` | two letter country code -- see {ISO-3166-1-alpha-2}[ISO 3166-1 alpha-2]. Hint: In the past we used `iso-3166` as format. | `"GB"` *Hint:* It is `"GB"`, not `"UK"`, even though `"UK"` has seen some use at Zalando. 60 | | `string` | `iso-4217` | three letter currency code -- see {ISO-4217}[ISO 4217] | `"EUR"` 61 | | `string` | `gtin-13` | Global Trade Item Number -- see {GTIN}[GTIN] | `"5710798389878"` 62 | |===================================================================== 63 | 64 | *Remark:* Please note that this list of standard data formats is not exhaustive 65 | and everyone is encouraged to propose additions. 66 | 67 | 68 | [#171] 69 | == {MUST} define a format for number and integer types 70 | 71 | In <<238>> we added `bigint` and `decimal` to the OpenAPI defined formats. 72 | As an implication, you must always provide one of the formats `int32`, `int64`, `bigint` 73 | or `float`, `double`, `decimal` when you define an API property of 74 | JSON type `number` or `integer`. 75 | 76 | By this we prevent clients from guessing the precision incorrectly, and thereby 77 | changing the value unintentionally. The precision must be translated by clients 78 | and servers into the most specific language types; in Java, for instance, the `number` 79 | type with `decimal` format will translate into `BigDecimal` and `integer` type with 80 | `int32` format will translate to `int` or `Integer` Java types. 81 | 82 | 83 | [#239] 84 | === {MUST} encode binary data in `base64url` 85 | 86 | You may expose binary data. You must use a standard media type and data format, 87 | if applicable -- see <<168, Rule 168>>. If no standard is available, you must define 88 | the binary data as `string` typed property with `binary` format using `base64url` 89 | encoding -- as also described in <<238>>. 90 | 91 | 92 | [#126] 93 | [#169] 94 | == {MUST} use standard formats for date and time properties 95 | 96 | As a specific case of <<238>>, you must use the `string` typed formats 97 | `date`, `date-time`, `time`, `duration`, or `period` for the definition of date and time properties. 98 | The formats are based on the standard {RFC-3339}[RFC 3339] internet profile -- a 99 | subset of https://tools.ietf.org/html/rfc3339#ref-ISO8601[ISO 8601]. 100 | 101 | APIs {MUST} use the upper-case `T` as a separator between date and time and 102 | upper-case `Z` at the end when generating dates as opposed to lower-case `t`, 103 | `z`, space, or any other character. This is stricter than the 104 | https://datatracker.ietf.org/doc/html/rfc3339#section-5.6[date/time format 105 | as defined in RFC 3339], which leaves it up to the specification. 106 | 107 | *Exception:* For passing date/time information via standard protocol headers, 108 | HTTP https://tools.ietf.org/html/rfc7231#section-7.1.1.1[RFC 7231] requires to 109 | follow the date and time specification used by the Internet Message Format 110 | https://tools.ietf.org/html/rfc5322[RFC 5322]. 111 | 112 | As defined by the standard, time zone offset may be used, however, we recommend 113 | to only use times based on UTC without local offsets. For example `2015-05-28T14:07:17Z` 114 | rather than `2015-05-28T14:07:17+00:00`. From experience we have learned that zone 115 | offsets are not easy to understand and often not correctly handled. Note also that 116 | zone offsets are different from local times which may include daylight saving time. 117 | When it comes to storage, all dates should be consistently stored in UTC without 118 | a zone offset. Localization should be done locally by the services that provide 119 | user interfaces, if required. 120 | 121 | *Hint:* We discourage using numerical timestamps. It typically creates 122 | issues with precision, e.g. whether to represent a timestamp as 1460062925, 123 | 1460062925000 or 1460062925.000. Date strings, though more verbose and requiring 124 | more effort to parse, avoid this ambiguity. 125 | 126 | 127 | [#255] 128 | == {SHOULD} select appropriate one of date or date-time format 129 | 130 | When choosing between `date` and `datetime` formats you should take into account the following: 131 | 132 | * `date` should be used for properties where no exact point in time is required and day time-range is sufficient, 133 | for instance, document dates, birthdays, ETAs (estimated time of arrival). 134 | Without further context, `date` implies the time period from midnight to midnight in the local time zone. 135 | However, the timezone information can be also provided 136 | as an additional context information via other fields indicating location. 137 | * `datetime` should be used in all other cases where an exact point in time is required, 138 | for instance, datetimes for supplier advice, specific processing events, fast delivery planning dates. 139 | As required in <<169>>, `datetime` requires the explicit time zone offset to be provided, 140 | which avoids misinterpretations and eliminates the need of an additional context to provide. 141 | 142 | 143 | [#127] 144 | == {SHOULD} use standard formats for time duration and interval properties 145 | 146 | Properties and that are by design durations and time intervals should be 147 | represented as strings formatted as defined by {ISO-8601}[ISO 8601] 148 | ({RFC-3339}#appendix-A[RFC 3339 Appendix A contains a grammar] for `durations` 149 | and `periods` - the latter called time intervals in {ISO-8601}[ISO 8601]). ISO 150 | 8601:1-2019 defines an extension (`..`) to express open ended time intervals 151 | that are very convenient in searches and are included in the below 152 | {RFC-2234}[ABNF] grammar: 153 | 154 | [source,abnf] 155 | ---- 156 | dur-second = 1*DIGIT "S" 157 | dur-minute = 1*DIGIT "M" [dur-second] 158 | dur-hour = 1*DIGIT "H" [dur-minute] 159 | dur-time = "T" (dur-hour / dur-minute / dur-second) 160 | dur-day = 1*DIGIT "D" 161 | dur-week = 1*DIGIT "W" 162 | dur-month = 1*DIGIT "M" [dur-day] 163 | dur-year = 1*DIGIT "Y" [dur-month] 164 | dur-date = (dur-day / dur-month / dur-year) [dur-time] 165 | duration = "P" (dur-date / dur-time / dur-week) 166 | 167 | period-explicit = iso-date-time "/" iso-date-time 168 | period-start = iso-date-time "/" (duration / "..") 169 | period-end = (duration / "..") "/" iso-date-time 170 | period = period-explicit / period-start / period-end 171 | ---- 172 | 173 | A time interval query parameter should use `_between` instead 174 | of the parameter pair `_before`/`_after`, while 175 | properties providing a time interval should be named `_interval`. 176 | 177 | 178 | [#128] 179 | [#170] 180 | == {MUST} use standard formats for country, language and currency properties 181 | 182 | As a specific case of <<238>> you must use the following standard formats: 183 | 184 | * Country codes: {ISO-3166-1-alpha-2}[ISO 3166-1-alpha-2] two letter country 185 | codes indicated via format `iso-3166-alpha-2` in the OpenAPI specification. 186 | * Language codes: {ISO-639-1}[ISO 639-1] two letter language codes indicated 187 | via format `iso-639-1` in the OpenAPI specification. 188 | * Language variant tags: {BCP47}[BCP 47] multi letter language tag indicated 189 | via format `bcp47` in the OpenAPI specification. (It is a compatible extension 190 | of {ISO-639-1}[ISO 639-1] with additional optional information for language 191 | usage, like region, variant, script) 192 | * Currency codes: {ISO-4217}[ISO 4217] three letter currency codes indicated 193 | via format `iso-4217` in the OpenAPI specification. 194 | 195 | [#244] 196 | == {SHOULD} use content negotiation, if clients may choose from different resource representations 197 | 198 | In some situations the API supports serving different representations of a 199 | specific resource (at the same URL), e.g. JSON, PDF, TEXT, or HTML 200 | representations for an invoice resource. You should use 201 | https://en.wikipedia.org/wiki/Content_negotiation[content negotiation] to 202 | support clients specifying via the standard HTTP headers {Accept}, 203 | {Accept-Language}, {Accept-Encoding} which representation is best suited for 204 | their use case, for example, which language of a document, representation / 205 | content format, or content encoding. You <<172>> like `application/json` or 206 | `application/pdf` for defining the content format in the {Accept} header. 207 | 208 | 209 | [#144] 210 | == {SHOULD} only use UUIDs if necessary 211 | 212 | Generating IDs can be a scaling problem in high frequency and near real time 213 | use cases. UUIDs solve this problem, as they can be generated without 214 | collisions in a distributed, non-coordinated way and without additional server 215 | round trips. 216 | 217 | However, they also come with some disadvantages: 218 | 219 | * pure technical key without meaning; not ready for naming or name scope 220 | conventions that might be helpful for pragmatic reasons, e.g. we learned to 221 | use names for product attributes, instead of UUIDs 222 | * less usable, because... 223 | ** cannot be memorized and easily communicated by humans 224 | ** harder to use in debugging and logging analysis 225 | ** less convenient for consumer facing usage 226 | * quite long: readable representation requires 36 characters and comes with 227 | higher memory and bandwidth consumption 228 | * not ordered along their creation history and no indication of used id volume 229 | * may be in conflict with additional backward compatibility support of legacy ids 230 | 231 | UUIDs should be avoided when not needed for large scale id generation. Instead, 232 | for instance, server side support with id generation can be preferred ({POST} 233 | on id resource, followed by idempotent {PUT} on entity resource). Usage of 234 | UUIDs is especially discouraged as primary keys of master and configuration 235 | data, like brand-ids or attribute-ids which have low id volume but widespread 236 | steering functionality. 237 | 238 | Please be aware that sequential, strictly monotonically increasing numeric 239 | identifiers may reveal critical, confidential business information, like order 240 | volume, to non-privileged clients. 241 | 242 | In any case, we should always use string rather than number type for 243 | identifiers. This gives us more flexibility to evolve the identifier naming 244 | scheme. Accordingly, if used as identifiers, UUIDs should not be qualified 245 | using a format property. 246 | 247 | Hint: Usually, random UUID is used - see UUID version 4 in {RFC-4122}[RFC 4122]. 248 | Though UUID version 1 also contains leading timestamps it is not reflected by 249 | its lexicographic sorting. This deficit is addressed by 250 | https://github.com/alizain/ulid[ULID] (Universally Unique Lexicographically 251 | Sortable Identifier). You may favour ULID instead of UUID, for instance, for 252 | pagination use cases ordered along creation time. 253 | -------------------------------------------------------------------------------- /chapters/deprecation.adoc: -------------------------------------------------------------------------------- 1 | [[deprecation]] 2 | = REST Design - Deprecation 3 | 4 | Sometimes it is necessary to phase out an API endpoint, an API version, or an 5 | API feature, e.g. if a field or parameter is no longer supported or a whole 6 | business functionality behind an endpoint is supposed to be shut down. As long 7 | as the API endpoints and features are still used by consumers these shut downs 8 | are breaking changes and not allowed. To progress the following deprecation 9 | rules have to be applied to make sure that the necessary consumer changes and 10 | actions are well communicated and aligned using _deprecation_ and _sunset_ 11 | dates. 12 | 13 | 14 | [#187] 15 | == {MUST} reflect deprecation in API specifications 16 | 17 | The API deprecation must be part of the API specification. 18 | 19 | If an API endpoint (operation object), an input argument (parameter object), 20 | an in/out data object (schema object), or on a more fine grained level, a 21 | schema attribute or property should be deprecated, the producers must set 22 | `deprecated: true` for the affected element and add further explanation to the 23 | `description` section of the API specification. If a future shut down is 24 | planned, the producer must provide a sunset date and document in details 25 | what consumers should use instead and how to migrate. 26 | 27 | 28 | [#185] 29 | == {MUST} obtain approval of clients before API shut down 30 | 31 | Before shutting down an API, version of an API, or API feature the producer 32 | must make sure, that all clients have given their consent on a sunset date. 33 | Producers should help consumers to migrate to a potential new API or API 34 | feature by providing a migration manual and clearly state the time line for 35 | replacement availability and sunset (see also <<189>>). Once all clients of 36 | a sunset API feature are migrated, the producer may shut down the deprecated 37 | API feature. 38 | 39 | 40 | [#186] 41 | == {MUST} collect external partner consent on deprecation time span 42 | 43 | If the API is consumed by any external partner, the API owner must define a 44 | reasonable time span that the API will be maintained after the producer has 45 | announced deprecation. All external partners must state consent with this 46 | after-deprecation-life-span, i.e. the minimum time span between official 47 | deprecation and first possible sunset, *before* they are allowed to use the 48 | API. 49 | 50 | 51 | [#188] 52 | == {MUST} monitor usage of deprecated API scheduled for sunset 53 | 54 | Owners of an API, API version, or API feature used in production that is 55 | scheduled for sunset must monitor the usage of the sunset API, API version, or 56 | API feature in order to observe migration progress and avoid uncontrolled 57 | breaking effects on ongoing consumers. See also <<193>>. 58 | 59 | 60 | [#189] 61 | == {SHOULD} add `Deprecation` and `Sunset` header to responses 62 | 63 | During the deprecation phase, the producer should add a `Deprecation: ` 64 | (see https://tools.ietf.org/html/draft-ietf-httpapi-deprecation-header[draft: RFC 65 | Deprecation HTTP Header Field]) and - if also planned - a `Sunset: ` 66 | (see {RFC-8594}#section-3[RFC 8594]) header on each response affected by a 67 | deprecated element (see <<187>>). 68 | 69 | The {Deprecation} header can either be set to `true` - if a feature is retired 70 | -, or carry a deprecation time stamp, at which a replacement will become/became 71 | available and consumers must not on-board any longer (see <<191>>). The optional 72 | {Sunset} time stamp carries the information when consumers latest have to stop 73 | using a feature. The sunset date should always offer an eligible time interval 74 | for switching to a replacement feature. 75 | 76 | [source,txt] 77 | ---- 78 | Deprecation: Tue, 31 Dec 2024 23:59:59 GMT 79 | Sunset: Wed, 31 Dec 2025 23:59:59 GMT 80 | ---- 81 | 82 | If multiple elements are deprecated the {Deprecation} and {Sunset} headers are 83 | expected to be set to the earliest time stamp to reflect the shortest interval 84 | at which consumers are expected to get active. The {Deprecation} and {Sunset} 85 | headers can be defined as follows in the API specification (see also the 86 | default definition below): 87 | 88 | [source,yaml] 89 | ---- 90 | components: 91 | parameters|headers: 92 | Deprecation: 93 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/headers-1.0.0.yaml#/Deprecation' 94 | Sunset: 95 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/headers-1.0.0.yaml#/Sunset' 96 | ---- 97 | 98 | [source,yaml] 99 | ---- 100 | include::../includes/deprecation.yaml[] 101 | include::../includes/sunset.yaml[lines=3..-1] 102 | ---- 103 | 104 | *Note:* adding the {Deprecation} and {Sunset} header is not sufficient to gain 105 | client consent to shut down an API or feature. 106 | 107 | *Hint:* In earlier guideline versions, we used the {Warning} header to provide 108 | the deprecation info to clients. However, {Warning} header has a less specific 109 | semantics, will be obsolete with 110 | https://tools.ietf.org/html/draft-ietf-httpbis-cache-06[draft: RFC HTTP 111 | Caching], and our syntax was not compliant with {RFC-9111}#section-5.5[RFC 9111 112 | Section 5.5 "Warning"]. 113 | 114 | 115 | [#190] 116 | == {SHOULD} add monitoring for `Deprecation` and `Sunset` header 117 | 118 | Clients should monitor the {Deprecation} and {Sunset} headers in HTTP responses 119 | to get information about future sunset of APIs and API features (see <<189>>). 120 | We recommend that client owners build alerts on this monitoring information to 121 | ensure alignment with service owners on required migration task. 122 | 123 | *Hint:* In earlier guideline versions, we used the `Warning` header to provide 124 | the deprecation info (see hint in <<189>>). 125 | 126 | 127 | [#191] 128 | == {MUST} not start using deprecated APIs 129 | 130 | Clients must not start using deprecated APIs, API versions, or API features. 131 | -------------------------------------------------------------------------------- /chapters/design-principles.adoc: -------------------------------------------------------------------------------- 1 | [[principles]] 2 | = Principles 3 | 4 | 5 | [[api-design-principles]] 6 | == API design principles 7 | 8 | Comparing SOA web service interfacing style of SOAP vs. REST, the former 9 | tend to be centered around operations that are usually use-case specific 10 | and specialized. In contrast, REST is centered around business (data) 11 | entities exposed as resources that are identified via URIs and can be 12 | manipulated via standardized CRUD-like methods using different 13 | representations, and hypermedia. RESTful APIs 14 | tend to be less use-case specific and come with less rigid client / 15 | server coupling and are more suitable for an ecosystem of (core) services 16 | providing a platform of APIs to build diverse new business services. 17 | We apply the RESTful web service principles to all kind of application 18 | (micro-) service components, independently from whether they provide 19 | functionality via the internet or intranet. 20 | 21 | * We prefer REST-based APIs with JSON payloads 22 | * We prefer systems to be truly RESTful 23 | footnote:fielding-restful[Per definition of R.Fielding REST APIs have to support 24 | HATEOAS (maturity level 3). Our guidelines do not strongly advocate for 25 | full REST compliance, but limited hypermedia usage, e.g. for pagination 26 | (see <>). 27 | However, we still use the term "RESTful API", due to the absence 28 | of an alternative established term and to keep it like the very majority 29 | of web service industry that also use the term for their REST 30 | approximations — in fact, in today's industry full HATEOAS compliant 31 | APIs are a very rare exception.] 32 | 33 | An important principle for API design and usage is Postel's 34 | Law, aka http://en.wikipedia.org/wiki/Robustness_principle[The 35 | Robustness Principle] (see also https://tools.ietf.org/html/rfc1122[RFC 1122]): 36 | 37 | * Be liberal in what you accept, be conservative in what you send 38 | 39 | _Readings:_ Some interesting reads on the RESTful API design style and service architecture: 40 | 41 | * Article: 42 | https://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling[REST API Design - Resource Modeling] 43 | * Article: 44 | https://martinfowler.com/articles/richardsonMaturityModel.html[Richardson Maturity Model -- Steps toward the glory of REST] 45 | * Book: 46 | https://www.amazon.de/Irresistible-APIs-Designing-that-developers/dp/1617292559[Irresistible 47 | APIs: Designing web APIs that developers will love] 48 | * Book: 49 | http://www.amazon.de/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829[REST 50 | in Practice: Hypermedia and Systems Architecture] 51 | * Book: https://leanpub.com/build-apis-you-wont-hate[Build APIs You 52 | Won't Hate] 53 | * Fielding Dissertation: 54 | http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm[Architectural 55 | Styles and the Design of Network-Based Software Architectures] 56 | 57 | 58 | [[api-as-a-product]] 59 | == API as a product 60 | 61 | As mentioned above, Zalando is transforming from an online shop into an 62 | expansive fashion platform comprising a rich set of products following a 63 | Software as a Platform (SaaP) model for our business partners. As a 64 | company we want to deliver products to our (internal and external) 65 | customers which can be consumed like a service. 66 | 67 | Platform products provide their functionality via (public) APIs; hence, 68 | the design of our APIs should be based on the API as a Product 69 | principle: 70 | 71 | * Treat your API as product and act like a product owner 72 | * Put yourself into the place of your customers; be an advocate for 73 | their needs 74 | * Emphasize simplicity, comprehensibility, and usability of APIs to 75 | make them irresistible for client engineers 76 | * Actively improve and maintain API consistency over the long term 77 | * Make use of customer feedback and provide service level support 78 | 79 | Embracing 'API as a Product' facilitates a service ecosystem, which can 80 | be evolved more easily and used to experiment quickly with new business 81 | ideas by recombining core capabilities. 82 | It makes the difference between agile, innovative product service 83 | business built on a platform of APIs and ordinary enterprise integration business 84 | where APIs are provided as "appendix" of existing products to support system integration 85 | and optimised for local server-side realization. 86 | 87 | Understand the concrete use cases of your customers and carefully check 88 | the trade-offs of your API design variants with a product mindset. Avoid short-term 89 | implementation optimizations at the expense of unnecessary client side 90 | obligations, and have a high attention on API quality and client 91 | developer experience. 92 | 93 | API as a Product is closely related to our <<100,API First principle>> 94 | (see next chapter) which is more focused on how we engineer high quality APIs. 95 | 96 | 97 | [[api-first]] 98 | == API first 99 | 100 | API First is one of our 101 | https://github.com/zalando/engineering-principles[engineering 102 | and architecture principles]. In a nutshell API First requires two 103 | aspects: 104 | 105 | * define APIs first, before coding its implementation, using a standard specification 106 | language 107 | * get early review feedback from peers and client developers 108 | 109 | By defining APIs outside the code, we want to facilitate early review 110 | feedback and also a development discipline that focus service interface 111 | design on... 112 | 113 | * profound understanding of the domain and required functionality 114 | * generalized business entities / resources, i.e. avoidance of use case 115 | specific APIs 116 | * clear separation of WHAT vs. HOW concerns, i.e. abstraction from 117 | implementation aspects — APIs should be stable even if we replace 118 | complete service implementation including its underlying technology 119 | stack 120 | 121 | Moreover, API definitions with standardized specification format also 122 | facilitate... 123 | 124 | * single source of truth for the API specification; it is a crucial part 125 | of a contract between service provider and client users 126 | * infrastructure tooling for API discovery, API GUIs, API documents, 127 | automated quality checks 128 | 129 | Elements of API First are also this API Guidelines and a standardized 130 | API review process as to get early review feedback from 131 | peers and client developers. Peer review is important for us to get high 132 | quality APIs, to enable architectural and design alignment and to 133 | supported development of client applications decoupled from service 134 | provider engineering life cycle. 135 | 136 | It is important to learn, that API First is *not in conflict with the 137 | agile development principles* that we love. Service applications should 138 | evolve incrementally — and so its APIs. Of course, our API specification 139 | will and should evolve iteratively in different cycles; however, each 140 | starting with draft status and _early_ team and peer review feedback. 141 | API may change and profit from implementation concerns and automated 142 | testing feedback. API evolution during development life cycle may 143 | include breaking changes for not yet productive features and as long as 144 | we have aligned the changes with the clients. Hence, API First does 145 | _not_ mean that you must have 100% domain and requirement understanding 146 | and can never produce code before you have defined the complete API and 147 | get it confirmed by peer review. 148 | 149 | On the other hand, API First obviously is in conflict with the bad 150 | practice of publishing API definition and asking for peer review after 151 | the service integration or even the service productive operation has 152 | started. It is crucial to request and get early feedback — as early as 153 | possible, but not before the API changes are comprehensive with focus 154 | to the next evolution step and have a certain quality (including API 155 | Guideline compliance), already confirmed via team internal reviews. 156 | -------------------------------------------------------------------------------- /chapters/general-guidelines.adoc: -------------------------------------------------------------------------------- 1 | [[general-guidelines]] 2 | = General guidelines 3 | 4 | The titles are marked with the corresponding labels: {MUST}, 5 | {SHOULD}, {MAY}. 6 | 7 | 8 | [#100] 9 | == {MUST} follow API first principle 10 | 11 | You must follow the <>, more specifically: 12 | 13 | * You must define APIs first, before coding its implementation, <<101, using 14 | OpenAPI as specification language>> 15 | * You must design your APIs consistently with these guidelines; use our 16 | {zally-ui}[API Linter Service (internal_link)] 17 | for automated rule checks. 18 | * You must call for early review feedback from peers and client developers, and apply 19 | {api-review-proc}[our lightweight API review process (internal_link)] 20 | for all component external APIs, i.e. all apis 21 | with `x-api-audience =/= component-internal` (see <<219>>). 22 | 23 | 24 | [#101] 25 | == {MUST} provide API specification using OpenAPI 26 | 27 | We use the http://swagger.io/specification/[OpenAPI specification] as standard 28 | to define API specification files. API designers are required to provide the API 29 | specification using a single *self-contained YAML* file to improve readability. 30 | We encourage to use *OpenAPI 3.0* version, but still support *OpenAPI 2.0* 31 | (a.k.a. Swagger 2). 32 | 33 | The API specification files should be subject to version control using a source 34 | code management system - best together with the implementing sources. 35 | 36 | You <<192, must / should publish>> the component <<219, external / internal>> 37 | API specification with the deployment of the implementing service, and, hence, 38 | make it discoverable for the group via our {api-portal}[API Portal (internal_link)]. 39 | 40 | *Hint:* A good way to explore *OpenAPI 3.0/2.0* is to navigate through the 41 | https://openapi-map.apihandyman.io/[OpenAPI specification mind map] and use 42 | our https://plugins.jetbrains.com/search?search=swagger+Monte[Swagger Plugin 43 | for IntelliJ IDEA] to create your first API. To explore and validate/evaluate 44 | existing APIs the https://editor.swagger.io/[Swagger Editor] or our 45 | https://apis.zalando.net[API Portal] may be a good starting point. 46 | 47 | *Hint:* We do not yet provide guidelines for https://graphql.org/[GraphQL] 48 | and focus on resource oriented HTTP/REST API style (and related tooling 49 | and infrastructure support). 50 | Following our {techradar-public}[Zalando Tech Radar (internal_link)], we think 51 | that GraphQL has no major benefits, but a couple of downsides compared to REST 52 | as API technology for general purpose peer-to-peer microservice communication. 53 | However, GraphQL can provide a lot of value for specific target domain problems, 54 | especially backends for frontends (BFF) and mobile clients, where typically 55 | many (domain object) resources from different services are queried and 56 | multiple roundtrip overhead should be avoided due to (mobile or public) 57 | network constraints. Therefore we list both technologies on ADOPT, though 58 | GraphQL only supplements REST for the BFF-specific problem domain. 59 | 60 | 61 | [#102] 62 | == {SHOULD} provide API user manual 63 | 64 | In addition to the API Specification, it is good practice to provide an API 65 | user manual to improve client developer experience, especially of engineers 66 | that are less experienced in using this API. A helpful API user manual 67 | typically describes the following API aspects: 68 | 69 | * API scope, purpose, and use cases 70 | * concrete examples of API usage 71 | * edge cases, error situation details, and repair hints 72 | * architecture context and major dependencies - including figures and 73 | sequence flows 74 | 75 | The user manual must be published online, e.g. via our documentation hosting 76 | platform service, GHE pages, or specific team web servers. Please do not forget 77 | to include a link to the API user manual into the API specification using the 78 | `#/externalDocs/url` property. 79 | 80 | 81 | [#103] 82 | == {MUST} write APIs using U.S. English 83 | 84 | 85 | [#234] 86 | == {MUST} only use durable and immutable remote references 87 | 88 | Normally, API specification files must be *self-contained*, i.e. files 89 | should not contain references to local or remote content, e.g. `../fragment.yaml#/element` or 90 | `$ref: 'https://github.com/zalando/zally/blob/master/server/src/main/resources/api/zally-api.yaml#/schemas/LintingRequest'`. 91 | The reason is, that the content referred to is _in general_ *not durable* and 92 | *not immutable*. As a consequence, the semantic of an API may change in 93 | unexpected ways. (For example, the second link is already outdated due to code restructuring.) 94 | 95 | However, you may use remote references to resources accessible by the following 96 | service URLs: 97 | 98 | * `{api-repository} (internal_link)` {dash} used 99 | to refer to user-defined, immutable API specification revisions published via the 100 | internal API repository. 101 | * `https://opensource.zalando.com/restful-api-guidelines/{model.yaml}` {dash} used 102 | to refer to guideline-defined re-usable API fragments (see `{model.yaml}` files in 103 | https://github.com/zalando/restful-api-guidelines/tree/main/models[restful-api-guidelines/models] 104 | for details). 105 | 106 | *Hint:* The formerly used remote references to the `Problem` API fragment 107 | (aliases `https://opensource.zalando.com/problem/` and 108 | `https://zalando.github.io/problem/`) are deprecated, but still supported for 109 | compatibility (<<176>> on how to replace). 110 | 111 | As we control these URLs, we ensure that their content is *durable* and 112 | *immutable*. This allows to define API specifications by using fragments 113 | published via these sources, as suggested in <<151>>. 114 | -------------------------------------------------------------------------------- /chapters/hyper-media.adoc: -------------------------------------------------------------------------------- 1 | [[hypermedia]] 2 | = REST Design - Hypermedia 3 | 4 | 5 | [#162] 6 | == {MUST} use REST maturity level 2 7 | 8 | We strive for a good implementation of 9 | http://martinfowler.com/articles/richardsonMaturityModel.html#level2[REST 10 | Maturity Level 2] as it enables us to build resource-oriented APIs that 11 | make full use of HTTP verbs and status codes. You can see this expressed 12 | by many rules throughout these guidelines, e.g.: 13 | 14 | * <<138>> 15 | * <<141>> 16 | * <<148>> 17 | * <<150>> 18 | 19 | Although this is not HATEOAS, it should not prevent you from designing 20 | proper link relationships in your APIs as stated in rules below. 21 | 22 | 23 | [#163] 24 | == {MAY} use REST maturity level 3 - HATEOAS 25 | 26 | We do not generally recommend to implement 27 | http://martinfowler.com/articles/richardsonMaturityModel.html#level3[REST 28 | Maturity Level 3]. HATEOAS comes with additional API complexity without 29 | real value in our SOA context where client and server interact via REST 30 | APIs and provide complex business functions as part of our e-commerce 31 | SaaS platform. 32 | 33 | Our major concerns regarding the promised advantages of HATEOAS (see 34 | also 35 | https://www.infoq.com/news/2014/03/rest-at-odds-with-web-apis[RESTistential 36 | Crisis over Hypermedia APIs], 37 | https://jeffknupp.com/blog/2014/06/03/why-i-hate-hateoas/[Why I Hate 38 | HATEOAS] and others for a detailed discussion): 39 | 40 | * We follow the <<100,API First principle>> with APIs explicitly defined 41 | outside the code with standard specification language. HATEOAS does not 42 | really add value for SOA client engineers in terms of API 43 | self-descriptiveness: a client engineer finds necessary links and usage 44 | description (depending on resource state) in the API reference definition 45 | anyway. 46 | * Generic HATEOAS clients which need no prior knowledge about APIs and 47 | explore API capabilities based on hypermedia information provided, is a 48 | theoretical concept that we haven't seen working in practice and does not 49 | fit to our SOA set-up. The OpenAPI description format (and tooling based 50 | on OpenAPI) doesn't provide sufficient support for HATEOAS either. 51 | * In practice relevant HATEOAS approximations (e.g. following specifications 52 | like HAL or JSON API) support API navigation by abstracting from URL 53 | endpoint and HTTP method aspects via link types. So, Hypermedia does not 54 | prevent clients from required manual changes when domain model changes 55 | over time. 56 | * Hypermedia make sense for humans, less for SOA machine clients. We would 57 | expect use cases where it may provide value more likely in the frontend and 58 | human facing service domain. 59 | * Hypermedia does not prevent API clients to implement shortcuts and directly 60 | target resources without 'discovering' them. 61 | 62 | However, we do not forbid HATEOAS; you could use it, if you checked its 63 | limitations and still see clear value for your usage scenario that justifies 64 | its additional complexity. If you use HATEOAS please share experience and 65 | present your findings in the {api-guild}[API Guild (internal_link)]. 66 | 67 | 68 | [#164] 69 | == {MUST} use common hypertext controls 70 | 71 | When embedding links to other resources into representations you must use the 72 | common hypertext control object. It contains at least one attribute: 73 | 74 | * [[href]]{href}: The URI of the resource the hypertext control is linking to. 75 | All our API are using HTTP(s) as URI scheme. 76 | 77 | In API that contain any hypertext controls, the attribute name {href} is 78 | reserved for usage within hypertext controls. 79 | 80 | The schema for hypertext controls can be derived from this model: 81 | 82 | [source,yaml] 83 | ---- 84 | HttpLink: 85 | description: A base type of objects representing links to resources. 86 | type: object 87 | properties: 88 | href: 89 | description: Any URI that is using http or https protocol 90 | type: string 91 | format: uri 92 | required: 93 | - href 94 | ---- 95 | 96 | The name of an attribute holding such a `HttpLink` object specifies the 97 | relation between the object that contains the link and the linked resource. 98 | Implementations should use names from the {iana-link-relations}[IANA Link Relation Registry] 99 | whenever appropriate. As IANA link relation 100 | names use hyphen-case notation, while this guide enforces snake_case 101 | notation for attribute names, hyphens in IANA names have to be replaced 102 | with underscores (e.g. the IANA link relation type `version-history` 103 | would become the attribute `version_history`) 104 | 105 | Specific link objects may extend the basic link type with additional 106 | attributes, to give additional information related to the linked 107 | resource or the relationship between the source resource and the linked 108 | one. 109 | 110 | E.g. a service providing "Person" resources could model a person who is 111 | married with some other person with a hypertext control that contains 112 | attributes which describe the other person (`id`, `name`) but also the 113 | relationship "spouse" between the two persons (`since`): 114 | 115 | [source,json] 116 | ---- 117 | { 118 | "id": "446f9876-e89b-12d3-a456-426655440000", 119 | "name": "Peter Mustermann", 120 | "spouse": { 121 | "href": "https://...", 122 | "since": "1996-12-19", 123 | "id": "123e4567-e89b-12d3-a456-426655440000", 124 | "name": "Linda Mustermann" 125 | } 126 | } 127 | ---- 128 | 129 | Hypertext controls are allowed anywhere within a JSON model. While this 130 | specification would allow 131 | http://stateless.co/hal_specification.html[HAL], we actually don't 132 | recommend/enforce the usage of HAL anymore as the structural separation 133 | of meta-data and data creates more harm than value to the 134 | understandability and usability of an API. 135 | 136 | 137 | [#165] 138 | == {SHOULD} use simple hypertext controls for pagination and self-references 139 | 140 | For pagination and self-references a simplified form of the <<164, extensible 141 | common hypertext controls>> should be used to reduce the specification and 142 | cognitive overhead. It consists of a simple URI value in combination with the 143 | corresponding {iana-link-relations}[link relations], e.g. {next}, {prev}, {first}, 144 | {last}, or {self}. 145 | 146 | See <<164>> and <<161>> for more information and examples. 147 | 148 | 149 | [#217] 150 | == {MUST} use full, absolute URI for resource identification 151 | 152 | Links to other resource must always use full, absolute URI. 153 | 154 | *Motivation*: Exposing any form of relative URI (no matter if the relative 155 | URI uses an absolute or relative path) introduces avoidable client side 156 | complexity. It also requires clarity on the base URI, which might not be given 157 | when using features like embedding subresources. The primary advantage of 158 | non-absolute URI is reduction of the payload size, which is better achievable 159 | by following the recommendation to use <<156,gzip compression>> 160 | 161 | 162 | [#166] 163 | == {MUST} not use link headers with JSON entities 164 | 165 | For flexibility and precision, we prefer links to be directly embedded in the 166 | JSON payload instead of being attached using the uncommon link header syntax. 167 | As a result, the use of the {RFC-8288}#section-3[`Link` Header defined by RFC 168 | 8288] in conjunction with JSON media types is forbidden. 169 | -------------------------------------------------------------------------------- /chapters/introduction.adoc: -------------------------------------------------------------------------------- 1 | [[introduction]] 2 | = Introduction 3 | 4 | Zalando’s software architecture centers around decoupled microservices 5 | that provide functionality via RESTful APIs with a JSON payload. Small 6 | engineering teams own, deploy and operate these microservices in their 7 | AWS (team) accounts. Our APIs express most purely what our systems do, 8 | and are therefore highly valuable business assets. Designing 9 | high-quality, long-lasting APIs has become even more critical for us 10 | since we started developing our new open platform strategy, which 11 | transforms Zalando from an online shop into an expansive fashion 12 | platform. Our strategy emphasizes developing lots of public APIs for our 13 | external business partners to use via third-party applications. 14 | 15 | With this in mind, we’ve adopted "API First" as one of our key 16 | engineering principles. Microservices development begins with API 17 | definition outside the code and ideally involves ample peer-review 18 | feedback to achieve high-quality APIs. API First encompasses a set of 19 | quality-related standards and fosters a peer review culture including a 20 | lightweight review procedure. We encourage our teams to follow them to 21 | ensure that our APIs: 22 | 23 | * are easy to understand and learn 24 | * are general and abstracted from specific implementation and use cases 25 | * are robust and easy to use 26 | * have a common look and feel 27 | * follow a consistent RESTful style and syntax 28 | * are consistent with other teams’ APIs and our global architecture 29 | 30 | Ideally, all Zalando APIs will look like the same author created them. 31 | 32 | 33 | [[conventions-used-in-these-guidelines]] 34 | == Conventions used in these guidelines 35 | 36 | The requirement level keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", 37 | "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and 38 | "OPTIONAL" used in this document (case insensitive) are to be 39 | interpreted as described in https://www.ietf.org/rfc/rfc2119.txt[RFC 40 | 2119]. 41 | 42 | 43 | [[zalando-specific-information]] 44 | == Zalando specific information 45 | 46 | The purpose of our "RESTful API guidelines" is to define standards to 47 | successfully establish "consistent API look and feel" quality. The 48 | {api-guild}[API Guild (internal_link)] 49 | drafted and owns this document. Teams are responsible to fulfill 50 | these guidelines during API development and are encouraged to contribute 51 | to guideline evolution via pull requests. 52 | 53 | These guidelines will, to some extent, remain work in progress as our 54 | work evolves, but teams can confidently follow and trust them. 55 | 56 | In case guidelines are changing, following rules apply: 57 | 58 | * existing APIs don't have to be changed, but we recommend it 59 | * clients of existing APIs have to cope with these APIs based on 60 | outdated rules 61 | * new APIs have to respect the current guidelines 62 | 63 | Furthermore you should keep in mind that once an API becomes public 64 | externally available, it has to be re-reviewed and changed according to 65 | current guidelines - for sake of overall consistency. 66 | -------------------------------------------------------------------------------- /chapters/meta-information.adoc: -------------------------------------------------------------------------------- 1 | [[meta-information]] 2 | = REST Basics - Meta information 3 | 4 | 5 | [#218] 6 | == {MUST} contain API meta information 7 | API specifications must contain the following OpenAPI meta information 8 | to allow for API management: 9 | 10 | - `#/info/title` as (unique) identifying, functional descriptive name of the API 11 | - `#/info/version` to distinguish API specifications versions following 12 | <<116, semantic rules>> 13 | - `#/info/description` containing a proper description of the API 14 | - `#/info/contact/{name,url,email}` containing the responsible team 15 | 16 | Following OpenAPI extension properties *must* be provided in addition: 17 | 18 | - `#/info/x-api-id` unique identifier of the API (<<215, see rule 215>>) 19 | - `#/info/x-audience` intended target audience of the API (<<219, see rule 219>>) 20 | 21 | 22 | [#116] 23 | == {MUST} use semantic versioning 24 | 25 | OpenAPI allows to specify the API specification version in 26 | `#/info/version`. To share a common semantic of version information we 27 | expect API designers to comply to http://semver.org/spec/v2.0.0.html[ 28 | Semantic Versioning 2.0] rules `1` to `8` and `11` restricted to the format 29 | .. for versions as follows: 30 | 31 | * Increment the **MAJOR** version when you make incompatible API changes 32 | after having aligned the changes with consumers, 33 | * Increment the **MINOR** version when you add new functionality in a 34 | backwards-compatible manner, and 35 | * Optionally increment the **PATCH** version when you make 36 | backwards-compatible bug fixes or editorial changes not affecting the 37 | functionality. 38 | 39 | *Additional Notes:* 40 | 41 | * *Pre-release* versions (http://semver.org#spec-item-9[rule 9]) and 42 | *build metadata* (http://semver.org#spec-item-10[rule 10]) must not 43 | be used in API version information. 44 | * While patch versions are useful for fixing typos etc, API designers 45 | are free to decide whether they increment it or not. 46 | * API designers should consider to use API version `0.y.z` 47 | (http://semver.org/#spec-item-4[rule 4]) for initial API design. 48 | 49 | Example: 50 | 51 | [source,yaml] 52 | ---- 53 | openapi: 3.0.1 54 | info: 55 | title: Parcel Service API 56 | description: API for <...> 57 | version: 1.3.7 58 | <...> 59 | ---- 60 | 61 | 62 | [#215] 63 | == {MUST} provide API identifiers 64 | 65 | Each API specification must be provisioned with a globally unique and 66 | immutable API identifier. The API identifier is defined in the `info`-block 67 | of the OpenAPI specification and must conform to the following definition: 68 | 69 | [source,yaml] 70 | ---- 71 | /info/x-api-id: 72 | type: string 73 | format: urn 74 | pattern: ^[a-z0-9][a-z0-9-:.]{6,62}[a-z0-9]$ 75 | description: | 76 | Mandatory globally unique and immutable API identifier. The API 77 | id allows to track the evolution and history of an API specification 78 | as a sequence of versions. 79 | ---- 80 | 81 | API specifications will evolve and any aspect of an OpenAPI specification 82 | may change. We require API identifiers because we want to support API clients 83 | and providers with API lifecycle management features, like change trackability 84 | and history or automated backward compatibility checks. The immutable API 85 | identifier allows the identification of all API specification versions of an 86 | API evolution. By using <<116, API semantic version information>> or <<192, 87 | API publishing date>> as order criteria you get the *version* or 88 | *publication history* as a sequence of API specifications. 89 | 90 | *Note*: While it is nice to use human readable API identifiers based on 91 | self-managed URNs, it is recommend to stick to a UUID (freshly generated when 92 | first creating the API) to relief API designers from any urge of changing 93 | the API identifier while evolving the API. *Do not copy an API unless you 94 | immediately change the API identifier in it!* 95 | 96 | Example: 97 | 98 | [source,yaml] 99 | ---- 100 | openapi: 3.0.1 101 | info: 102 | x-api-id: d0184f38-b98d-11e7-9c56-68f728c1ba70 103 | title: Parcel Service API 104 | description: API for <...> 105 | version: 1.5.8 106 | <...> 107 | ---- 108 | 109 | 110 | [#219] 111 | == {MUST} provide API audience 112 | 113 | Each API must be classified with respect to the intended target *audience* 114 | supposed to consume the API, to facilitate differentiated standards on APIs 115 | for discoverability, changeability, quality of design and documentation, as 116 | well as permission granting. We differentiate the following API audience 117 | groups with clear organisational and legal boundaries: 118 | 119 | *component-internal*:: 120 | This is often referred to as a _team internal API_ or a _product internal API_. 121 | The API consumers with this audience are restricted to applications of the 122 | same *functional component* which typically represents a specific *product* 123 | with clear functional scope and ownership. 124 | All services of a functional component / product are owned by a specific dedicated owner 125 | and engineering team(s). Typical examples of component-internal APIs are APIs 126 | being used by internal helper and worker services or that support service operation. 127 | *business-unit-internal*:: 128 | The API consumers with this audience are restricted to applications of a 129 | specific product portfolio owned by the same business unit. 130 | *company-internal*:: 131 | The API consumers with this audience are restricted to applications owned 132 | by the business units of the same the company (e.g. Zalando company with 133 | Zalando SE, Zalando Payments SE & Co. KG. etc.) 134 | *external-partner*:: 135 | The API consumers with this audience are restricted to applications of 136 | business partners of the company owning the API and the company itself. 137 | *external-public*:: 138 | APIs with this audience can be accessed by anyone with Internet access. 139 | 140 | *Note:* a smaller audience group is intentionally included in the wider group 141 | and thus does not need to be declared additionally. 142 | 143 | The API audience is provided as API meta information in the `info`-block of 144 | the OpenAPI specification and must conform to the following specification: 145 | 146 | [source,yaml] 147 | ---- 148 | /info/x-audience: 149 | type: string 150 | x-extensible-enum: 151 | - component-internal 152 | - business-unit-internal 153 | - company-internal 154 | - external-partner 155 | - external-public 156 | description: | 157 | Intended target audience of the API. Relevant for standards around 158 | quality of design and documentation, reviews, discoverability, 159 | changeability, and permission granting. 160 | ---- 161 | 162 | *Note:* Exactly *one audience* per API specification is allowed. For this 163 | reason a smaller audience group is intentionally included in the wider group 164 | and thus does not need to be declared additionally. If parts of your API have 165 | a different target audience, we recommend to split API specifications along 166 | the target audience — even if this creates redundancies 167 | ({api-audience-narrative}[rationale (internal_link)]). 168 | 169 | Example: 170 | 171 | [source,yaml] 172 | ---- 173 | openapi: 3.0.1 174 | info: 175 | x-audience: company-internal 176 | title: Parcel Helper Service API 177 | description: API for <...> 178 | version: 1.2.4 179 | <...> 180 | ---- 181 | 182 | For details and more information on audience groups see the 183 | {api-audience-narrative}[API Audience narrative (internal_link)]. 184 | 185 | 186 | [#223] 187 | == {MUST-SHOULD-MAY} use functional naming schema 188 | 189 | Functional naming is a powerful, yet easy way to align global resources as 190 | _host_, _permission_, and _event names_ within an application landscape. It 191 | helps to preserve uniqueness of names while giving readers meaningful context 192 | information about the addressed component. Besides, the most important aspect 193 | is, that it allows to keep APIs stable in the case of technical and 194 | organizational changes (Zalando for example maintains an internal naming convention). 195 | 196 | A unique `functional-name` is assigned to each functional component serving an API. 197 | It is built of the domain name of the functional group the component is belonging 198 | to and a unique a short identifier for the functional component itself: 199 | 200 | [source,bnf] 201 | ---- 202 | ::= - 203 | ::= [a-z][a-z0-9-]* -- managed functional group of components 204 | ::= [a-z][a-z0-9-]* -- name of API owning functional component 205 | ---- 206 | 207 | Depending on the <<219, API audience>>, you *must/should/may* follow the functional 208 | naming schema for <<224, hostnames>> and <<213, event names>> 209 | (and also <<225, permission names>>, in future) as follows: 210 | 211 | [cols="25%,75%,options="header"] 212 | |========================================================= 213 | | *Functional Naming* | *Audience* 214 | | *must* | external-public, external-partner 215 | | *should* | company-internal, business-unit-internal 216 | | *may* | component-internal 217 | |========================================================= 218 | 219 | Please see the following rules for detailed functional naming patterns: 220 | * <<224>> 221 | * <<213>> 222 | // * <<225>> 223 | 224 | 225 | *Internal Guideance*: You _must_ use the simple 226 | {functional-name-registry}[functional name registry (internal_link)] 227 | to register your functional name before using 228 | it. The registry is a centralized infrastructure service to ensure uniqueness 229 | of your functional names (and available domains -- including subdomains) and 230 | to support hostname DNS resolution. + 231 | _Hint:_ Due to lexicalic restrictions of DNS names there is no specific separator 232 | to split a functional name into (sub) domain and component; this knowledge is only 233 | managed in the registry. 234 | 235 | 236 | [#224] 237 | == {MUST} follow naming convention for hostnames 238 | 239 | Hostnames in APIs must, respectively should conform to the functional naming 240 | depending on the <<219, audience>> as follows (see <<223>> for details and 241 | `` definition): 242 | 243 | [source,bnf] 244 | ----- 245 | ::= | 246 | 247 | ::= .zalandoapis.com 248 | ----- 249 | 250 | *Hint:* The following convention (e.g. used by legacy STUPS infrastructure) is deprecated 251 | and *only* allowed for hostnames of <<219, component-internal>> APIs: 252 | 253 | [source,bnf] 254 | ----- 255 | ::= ..zalan.do 256 | ::= [a-z][a-z0-9-]* -- application identifier 257 | ::= [a-z][a-z0-9-]* -- organization unit identifier, e.g. team identifier 258 | ----- 259 | 260 | *Exception:* There are legacy hostnames used for APIs with `external-partner` audience 261 | which may not follow this rule due to backward compatibility constraints. 262 | The API Linter maintains an allow-list for these exceptions (including e.g. 263 | `api.merchants.zalando.com` and `api-sandbox.merchants.zalando.com`). 264 | -------------------------------------------------------------------------------- /chapters/pagination.adoc: -------------------------------------------------------------------------------- 1 | [[pagination]] 2 | = REST Design - Pagination 3 | 4 | 5 | [#159] 6 | == {MUST} support pagination 7 | 8 | Access to lists of data items must support pagination to protect the service 9 | against overload as well as to support client side iteration and batch 10 | processing experience. This holds true for all lists that are (potentially) 11 | larger than just a few hundred entries. 12 | 13 | There are two well known page iteration techniques: 14 | 15 | * **Offset-based pagination**: numeric offset identifies the first page-entry 16 | * **Cursor-based pagination** — aka key-based pagination: a unique key 17 | identifies the first page-entry (see also 18 | https://dev.twitter.com/overview/api/cursoring[Twitter API] or 19 | https://developers.facebook.com/docs/graph-api/results[Facebook API]) 20 | 21 | :smashing-pagination: https://www.smashingmagazine.com/2016/03/pagination-infinite-scrolling-load-more-buttons/ 22 | 23 | The technical conception of pagination should also consider user experience 24 | (see {smashing-pagination}[Pagination Usability Findings In eCommerce]), for 25 | instance, jumping to a specific page is far less used than navigation via 26 | {next}/{prev} page links (see <<161>>). This favors an API design using 27 | cursor-based instead of offset-based pagination -- see <<160>>. 28 | 29 | **Note:** To provide a consistent look and feel of pagination patterns, 30 | you must stick to the common query parameter names defined in <<137>>. 31 | 32 | 33 | 34 | [#160] 35 | == {SHOULD} prefer cursor-based pagination, avoid offset-based pagination 36 | 37 | Cursor-based pagination is usually better and more efficient when compared to 38 | offset-based pagination. Especially when it comes to high-data volumes and/or 39 | storage in NoSQL databases. 40 | 41 | Before choosing cursor-based pagination, consider the following trade-offs: 42 | 43 | * Usability/framework support: 44 | ** Offset-based pagination is more widely known than cursor-based pagination, 45 | so it has more framework support and is easier to use for API clients. 46 | * Use case - jump to a certain page: 47 | ** If jumping to a particular page in a range (e.g., 51 of 100) is really a 48 | required use case, cursor-based navigation may not be feasible. 49 | * Data changes may lead to anomalies in result pages: 50 | ** Offset-based pagination may create duplicates or lead to missing entries 51 | if rows are inserted or deleted between two subsequent paging requests. 52 | ** If implemented incorrectly, cursor-based pagination may fail when the 53 | cursor entry has been deleted before fetching the pages. 54 | * Performance considerations - efficient server-side processing using 55 | offset-based pagination is hardly feasible for: 56 | ** Very big data sets, especially if they cannot reside in the main memory of 57 | the database. 58 | ** Sharded or NoSQL databases. 59 | 60 | The {cursor} used for pagination is an opaque pointer to a page, that must 61 | never be *inspected* or *constructed* by clients. It usually encodes (encrypts) 62 | the page position, i.e. the unique identifier of the first or last page 63 | element, the pagination direction, and the applied query filters (or a hash 64 | over these) to safely recreate the collection (see also best practice 65 | <> below). 66 | 67 | 68 | [#248] 69 | == {SHOULD} use pagination response page object 70 | 71 | [[pagination-fields]] 72 | For iterating over collections (result sets) we propose to either use cursors 73 | (see <<160>>) or simple hypertext control links (see <<161>>). To implement 74 | these in a consistent way, we have defined a response page object pattern with 75 | the following field semantics: 76 | 77 | * [[self]]{self}: the link or cursor pointing to the same page. 78 | * [[first]]{first}: the link or cursor pointing to the first page. 79 | * [[prev]]{prev}: the link or cursor pointing to the previous page. 80 | It is not provided, if it is the first page. 81 | * [[next]]{next}: the link or cursor pointing to the next page. 82 | It is not provided, if it is the last page. 83 | * [[last]]{last}: the link or cursor pointing to the last page. 84 | 85 | Pagination responses should contain the following additional array field to 86 | transport the page content: 87 | 88 | * [[items]]{items}: array of resources, holding all the items of the current 89 | page ({items} may be replaced by a resource name). 90 | 91 | For responses to {GET-with-body} operations, the applied query filters **should** be 92 | (and for normal {GET} may be) returned using the following field: 93 | 94 | * [[query]]{query}: object containing the query filters applied in the search 95 | request to filter the collection resource. This can be directly used as the 96 | request body when following the pagination links. 97 | 98 | In conclusion, the standard response page using plain <<160, cursors>> or <<161, 99 | pagination links>> may be defined as follows: 100 | 101 | [source,yaml] 102 | ---- 103 | ResponsePage: 104 | type: object 105 | required: 106 | - items 107 | properties: 108 | self: 109 | description: Pagination link|cursor pointing to the current page. 110 | type: string 111 | format: uri|cursor 112 | first: 113 | description: Pagination link|cursor pointing to the first page. 114 | type: string 115 | format: uri|cursor 116 | prev: 117 | description: Pagination link|cursor pointing to the previous page. 118 | type: string 119 | format: uri|cursor 120 | next: 121 | description: Pagination link|cursor pointing to the next page. 122 | type: string 123 | format: uri|cursor 124 | last: 125 | description: Pagination link|cursor pointing to the last page. 126 | type: string 127 | format: uri|cursor 128 | 129 | query: 130 | description: > 131 | Object containing the query filters applied to the collection resource. 132 | This can be directly used as a request body (together with the cursors/links) 133 | when requesting other pages. 134 | type: object 135 | properties: ... 136 | 137 | items: 138 | description: Array of collection items. 139 | type: array 140 | required: false 141 | items: 142 | type: ... 143 | ---- 144 | 145 | *Note:* While you may support plain cursors for {next}, {prev}, {first}, {last}, and 146 | {self}, it is best practice to replace these with pagination links -- see 147 | <<161>>. 148 | 149 | 150 | [#161] 151 | == {SHOULD} use pagination links 152 | 153 | To simplify client design, APIs should support <<165, simplified hypertext 154 | controls>> as standard pagination links where applicable: 155 | 156 | [source,json] 157 | ---- 158 | { 159 | "self": "https://my-service.zalandoapis.com/resources?cursor=", 160 | "first": "https://my-service.zalandoapis.com/resources?cursor=", 161 | "prev": "https://my-service.zalandoapis.com/resources?cursor=", 162 | "next": "https://my-service.zalandoapis.com/resources?cursor=", 163 | "last": "https://my-service.zalandoapis.com/resources?cursor=", 164 | "query": { 165 | "query-param-<1>": ..., 166 | "query-param-": ... 167 | }, 168 | "items": [...] 169 | } 170 | ---- 171 | 172 | For {GET-with-body} operations, the `query` object can to be used as a request 173 | body with either of these links. (It should be equivalent to just resend 174 | the original body.) 175 | 176 | See also <<248>> for details on the pagination fields and page result object. 177 | 178 | 179 | [#254] 180 | == {SHOULD} avoid a total result count 181 | 182 | In pagination responses you should generally avoid providing a _total result 183 | count_, since calculating it is a costly operation that is usually not required 184 | by clients. Counting the total number of results for complex queries usually 185 | requires a full scan of all involved indexes, as it is difficult to calculate 186 | and cache it in advance. While this is only an implementation detail, it is 187 | important to consider that providing these total counts over the life-span 188 | of a service might become expensive as the data set grows over time. 189 | 190 | As clients may integrate against these counts over time alongside data 191 | set growth, removing them will be more difficult than not providing them 192 | in the first place. 193 | 194 | If your consumer really requires a total result count in the response, you may 195 | support this requirement via the {Prefer} header adding the directive 196 | `return=total-count` (see also <<181>>). 197 | -------------------------------------------------------------------------------- /chapters/performance.adoc: -------------------------------------------------------------------------------- 1 | [[performance]] 2 | = REST Design - Performance 3 | 4 | 5 | [#155] 6 | == {SHOULD} reduce bandwidth needs and improve responsiveness 7 | 8 | APIs should support techniques for reducing bandwidth based on client needs. 9 | This holds for APIs that (might) have high payloads and/or are used in 10 | high-traffic scenarios like the public Internet and telecommunication networks. 11 | Typical examples are APIs used by mobile web app clients with (often) less 12 | bandwidth connectivity. (Zalando is a 'Mobile First' company, so be mindful of 13 | this point.) 14 | 15 | Common techniques include: 16 | 17 | * compression of request and response bodies (see <<156>>) 18 | * querying field filters to retrieve a subset of resource attributes (see 19 | <<157>> below) 20 | * {ETag} and {If-Match}/{If-None-Match} headers to avoid re-fetching of 21 | unchanged resources (see <<182>>) 22 | * {Prefer} header with `return=minimal` or `respond-async` to anticipate reduced 23 | processing requirements of clients (see <<181>>) 24 | * <> for incremental access of larger collections of data items 25 | * caching of master data items, i.e. resources that change rarely or not 26 | at all after creation (see <<227>>). 27 | 28 | Each of these items is described in greater detail below. 29 | 30 | 31 | [#156] 32 | == {SHOULD} use `gzip` compression 33 | 34 | A servers and clients should support `gzip` content encoding to reduce the data 35 | transported over the network and thereby speed up response times, unless there 36 | is a good reason against this. Good reasons against compression are: 37 | 38 | 1. The content is already compressed, or 39 | 2. The server has not enough resources to support compression. 40 | 41 | While `gzip` content encoding should be the default, servers must also support 42 | unencoded content for testing. This is ensured by content negotiation using the 43 | {Accept-Encoding} request header (see {RFC-9110}#section-12.5.3[RFC 9110 44 | Section 12.5.3]). Successful compression is signaled via the {Content-Encoding} 45 | response header (see {RFC-9110}#section-8.4[RFC 9110 Section 8.4]). Clients and 46 | servers may support other compression algorithms as `compress`, `deflate`, or 47 | `br`. 48 | 49 | To signal server support for compression the API specification should define 50 | both, the {Accept-Encoding} request header and the {Content-Encoding} response 51 | header. This can be done by simply referencing the standard header definitions 52 | as follows (see also the default definition below): 53 | 54 | [source,yaml] 55 | ---- 56 | components: 57 | parameters|headers: 58 | Accept-Encoding: 59 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/headers-1.0.0.yaml#/Accept-Encoding' 60 | Content-Encoding: 61 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/headers-1.0.0.yaml#/Content-Encoding' 62 | ---- 63 | 64 | [source,yaml] 65 | ---- 66 | include::../includes/accept-encoding.yaml[] 67 | include::../includes/content-encoding.yaml[line=3..-1] 68 | ---- 69 | 70 | *Note:* since most server and client frameworks still **do not** support 71 | `gzip`-compression *out-of-the-box* you need to manually activate it, e.g. 72 | https://www.callicoder.com/configuring-spring-boot-application/#enabling-gzip-compression-in-spring-boot[Spring Boot], 73 | or add a dedicated middleware, e.g. https://github.com/gin-contrib/gzip#usage[gin-gionic/gin]. 74 | 75 | 76 | [#157] 77 | == {SHOULD} support partial responses via filtering 78 | 79 | Depending on your use case and payload size, you can significantly reduce 80 | network bandwidth need by supporting filtering of returned entity fields. 81 | Here, the client can explicitly determine the subset of fields he wants to 82 | receive via the {fields} query parameter. (It is analogue to 83 | https://graphql.org/learn/queries/#fields[GraphQL `fields`] and simple 84 | queries, and also applied, for instance, for 85 | https://cloud.google.com/storage/docs/json_api/v1/how-tos/performance#partial-response[Google 86 | Cloud API's partial responses].) 87 | 88 | 89 | [[unfiltered]] 90 | === Unfiltered 91 | 92 | [source,http] 93 | ---- 94 | GET http://api.example.org/users/123 HTTP/1.1 95 | 96 | HTTP/1.1 200 OK 97 | Content-Type: application/json 98 | 99 | { 100 | "id": "cddd5e44-dae0-11e5-8c01-63ed66ab2da5", 101 | "name": "John Doe", 102 | "address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States", 103 | "birthday": "1984-09-13", 104 | "friends": [ { 105 | "id": "1fb43648-dae1-11e5-aa01-1fbc3abb1cd0", 106 | "name": "Jane Doe", 107 | "address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States", 108 | "birthday": "1988-04-07" 109 | } ] 110 | } 111 | ---- 112 | 113 | 114 | [[filtered]] 115 | === Filtered 116 | 117 | [source,http] 118 | ---- 119 | GET http://api.example.org/users/123?fields=(name,friends(name)) HTTP/1.1 120 | 121 | HTTP/1.1 200 OK 122 | Content-Type: application/json 123 | 124 | { 125 | "name": "John Doe", 126 | "friends": [ { 127 | "name": "Jane Doe" 128 | } ] 129 | } 130 | ---- 131 | 132 | The {fields} query parameter determines the fields returned with the response 133 | payload object. For instance, `(name)` returns `users` root object with only 134 | the `name` field, and `(name,friends(name))` returns the `name` and the nested 135 | `friends` object with only its `name` field. 136 | 137 | OpenAPI doesn't support you in formally specifying different return object 138 | schemes depending on a parameter. When you define the field parameter, we 139 | recommend to provide the following description: `Endpoint supports filtering 140 | of return object fields as described in 141 | [Rule #157](https://opensource.zalando.com/restful-api-guidelines/#157)` 142 | 143 | The syntax of the query {fields} value is defined by the following 144 | https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form[BNF] grammar. 145 | 146 | [source,bnf] 147 | ---- 148 | ::= [ ] 149 | ::= "(" ")" 150 | ::= [ "," ] 151 | ::= | 152 | ::= 153 | ::= [ ] 154 | ::= | | 155 | ::= "-" | "_" 156 | ::= "A" | ... | "Z" | "a" | ... | "z" 157 | ::= "0" | ... | "9" 158 | ::= "!" 159 | ---- 160 | 161 | *Note:* Following the 162 | https://en.wikipedia.org/wiki/Principle_of_least_astonishment[principle of 163 | least astonishment], you should not define the {fields} query parameter using 164 | a default value, as the result is counter-intuitive and very likely not 165 | anticipated by the consumer. 166 | 167 | 168 | [#158] 169 | == {SHOULD} allow optional embedding of sub-resources 170 | 171 | Embedding related resources (also know as _Resource expansion_) is a 172 | great way to reduce the number of requests. In cases where clients know 173 | upfront that they need some related resources they can instruct the 174 | server to prefetch that data eagerly. Whether this is optimized on the 175 | server, e.g. a database join, or done in a generic way, e.g. an HTTP 176 | proxy that transparently embeds resources, is up to the implementation. 177 | 178 | See <<137>> for naming, e.g. "embed" for steering of embedded 179 | resource expansion. Please use the 180 | https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form[BNF] grammar, as 181 | already defined above for filtering, when it comes to an embedding query 182 | syntax. 183 | 184 | Embedding a sub-resource can possibly look like this where an order 185 | resource has its order items as sub-resource (/order/\{orderId}/items): 186 | 187 | [source,http] 188 | ---- 189 | GET /order/123?embed=(items) HTTP/1.1 190 | 191 | { 192 | "id": "123", 193 | "_embedded": { 194 | "items": [ 195 | { 196 | "position": 1, 197 | "sku": "1234-ABCD-7890", 198 | "price": { 199 | "amount": 71.99, 200 | "currency": "EUR" 201 | } 202 | } 203 | ] 204 | } 205 | } 206 | ---- 207 | 208 | 209 | [#227] 210 | == {MUST} document cacheable `GET`, `HEAD`, and `POST` endpoints 211 | 212 | Caching has to take many aspects into account, e.g. general <> of response information, our guideline to protect endpoints 214 | using SSL and <<104, OAuth authorization>>, resource update and invalidation 215 | rules, existence of multiple consumer instances. Caching is in best case 216 | complex, e.g. with respect to consistency, in worst case inefficient. 217 | 218 | As a consequence, client side as well as transparent web caching should be 219 | avoided, unless the service supports and requires it to protect itself, e.g. 220 | in case of a heavily used and therefore rate limited master data service, i.e. 221 | data items that rarely or not at all change after creation. 222 | 223 | As default, servers and clients should always set the {Cache-Control} header 224 | to {Cache-Control-no-store} and assume the same setting, if no {Cache-Control} 225 | header is provided. 226 | 227 | *Note:* There is no need to document this default setting. However, please make 228 | sure that your framework is attaching these header values by default, or ensure 229 | this manually, e.g. using the best practice of 230 | https://www.baeldung.com/spring-security-cache-control-headers[Spring Security] 231 | as shown below. Any setup deviating from this default must be sufficiently 232 | documented. 233 | 234 | [source,http] 235 | ---- 236 | Cache-Control: no-cache, no-store, must-revalidate, max-age=0 237 | ---- 238 | 239 | If your service really requires to support caching, please observe the 240 | following rules: 241 | 242 | * Document all <> {GET}, {HEAD}, and {POST} endpoints by declaring 243 | the support of {Cache-Control}, {Vary}, and {ETag} headers in response. 244 | *Note:* you must not define the {Expires} header to prevent redundant and 245 | ambiguous definition of cache lifetime. A sensible default documentation of 246 | these headers is given below. 247 | * Take care to specify the ability to support caching by defining the right 248 | caching boundaries, i.e. time-to-live and cache constraints, by providing 249 | sensible values for {Cache-Control} and {Vary} in your service. We will 250 | explain best practices below. 251 | * Provide efficient methods to warm up and update caches (see <>). 252 | 253 | Usually, you can reuse the standard {Cache-Control}, {Vary}, and {ETag} 254 | response header definitions provided by the guideline as follows: 255 | 256 | [source,yaml] 257 | ---- 258 | components: 259 | parameters|headers: 260 | Cache-Control: 261 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/headers-1.0.0.yaml#/Cache-Control' 262 | Vary: 263 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/headers-1.0.0.yaml#/Vary' 264 | ETag: 265 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/headers-1.0.0.yaml#/ETag' 266 | ---- 267 | 268 | See also <<182>>. 269 | 270 | [[cache-support-patterns]] 271 | === Cache Support Patterns 272 | 273 | To make best use of caching in micro service environments you need to provide 274 | efficient methods to warm up and update caches, e.g. as follows: 275 | 276 | * In general, you should support <<182, `ETag` Together With `If-Match`/ 277 | `If-None-Match` Header>> on all <> endpoints. 278 | * For larger data items you should support {HEAD} requests or more a bit more 279 | efficient {GET} requests with {If-None-Match} header to check for updates. 280 | * For small data sets you should provide a *get full collection* {GET} endpoint 281 | that supports an {ETag} for the collection in combination with a {HEAD} or 282 | {GET} requests with {If-None-Match} to check for updates. 283 | * For medium sized data sets provide a *get full collection* {GET} endpoint 284 | that supports an {ETag} for the collection in combination with <> 285 | and {entity-tag} filtering {GET} requests for limiting the response to 286 | changes since the provided {entity-tag}. *Note:* this is not supported by 287 | generic client and proxy caches on HTTP layer. 288 | 289 | *Hint:* For proper cache support, you must return {304} without content on a 290 | failed {HEAD} or {GET} request with <<182, `If-None-Match: `>> 291 | instead of {412}. For {ETag} see also <<182>>. 292 | 293 | ==== Default Header Values 294 | 295 | The default setting for {Cache-Control} should contain the `private` directive 296 | for endpoints with standard <<104, OAuth authorization>>, as well as the 297 | `must-revalidate` directive to ensure, that the client does not use stale cache 298 | entries. Last, the `max-age` directive should be set to a value between a few 299 | seconds (`max-age=60`) and a few hours (`max-age=86400`) depending on the change 300 | rate of your master data and your requirements to keep clients consistent. 301 | 302 | [source,http] 303 | ---- 304 | Cache-Control: private, must-revalidate, max-age=300 305 | ---- 306 | 307 | The default setting for {Vary} is harder to determine correctly. It depends on 308 | the API endpoint, e.g. whether it supports compression, accepts different media 309 | types, or requires other request specific headers. To support correct caching 310 | you have to carefully choose the value. However, a good first default may be: 311 | 312 | [source,http] 313 | ---- 314 | Vary: accept, accept-encoding 315 | ---- 316 | 317 | Anyhow, this is only relevant, if you encourage clients to install generic 318 | HTTP layer client and proxy caches. 319 | 320 | ==== Caching strategy 321 | 322 | Generic client and proxy caching on HTTP level is hard to configure. Therefore, 323 | we strongly recommend to attach the (possibly distributed) cache directly to 324 | the service (or gateway) layer of your application. This relieves the service 325 | from interpreting the {vary} header and greatly simplifies the usage patterns 326 | of the {Cache-Control} and {ETag} headers. Moreover, is highly efficient with 327 | respect to cache performance and overhead, and allows to support more 328 | <>. 329 | 330 | Anyhow, please carefully read {RFC-9111}[RFC 9111] before adding any client or 331 | proxy cache. 332 | -------------------------------------------------------------------------------- /chapters/references.adoc: -------------------------------------------------------------------------------- 1 | [[appendix-references]] 2 | [appendix] 3 | = References 4 | 5 | This section collects links to documents to which we refer, and base our guidelines on. 6 | 7 | 8 | [[openapi-specification]] 9 | == OpenAPI specification 10 | 11 | * *https://github.com/OAI/OpenAPI-Specification/[OpenAPI specification]* 12 | * *https://openapi-map.apihandyman.io/[OpenAPI specification mind map]* 13 | 14 | 15 | [[publications-specifications-and-standards]] 16 | == Publications, specifications and standards 17 | 18 | * *{RFC-3339}[RFC 3339]:* Date and Time on the Internet: Timestamps 19 | * *{RFC-4122}[RFC 4122]:* A Universally Unique IDentifier (UUID) URN Namespace 20 | * *{RFC-4627}[RFC 4627]:* The application/json Media Type for JavaScript Object Notation (JSON) 21 | * *{RFC-4648}[RFC 4648]:* The Base16, Base32, and Base64 Data Encodings 22 | * *{RFC-6585}[RFC 6585]:* Additional HTTP Status Codes 23 | * *{RFC-6902}[RFC 6902]:* JavaScript Object Notation (JSON) Patch 24 | * *{RFC-7159}[RFC 7159]:* The JavaScript Object Notation (JSON) Data Interchange Format 25 | * *{RFC-7240}[RFC 7240]:* Prefer Header for HTTP 26 | * *{RFC-7396}[RFC 7396]:* JSON Merge Patch 27 | * *{RFC-8288}[RFC 8288]:* Web Linking 28 | * *{RFC-9110}[RFC 9110]:* HTTP Semantics 29 | * *{RFC-9111}[RFC 9111]:* HTTP Caching 30 | * *{RFC-9457}[RFC 9457]:* Problem Details for HTTP APIs 31 | 32 | * *{ISO-8601}[ISO 8601]:* Date and time format 33 | * *{ISO-3166-1-alpha-2}[ISO 3166-1 alpha-2]:* Two letter country codes 34 | * *{ISO-639-1}[ISO 639-1]:* Two letter language codes 35 | * *{ISO-4217}[ISO 4217]:* Currency codes 36 | * *{BCP47}[BCP 47]:* Tags for Identifying Languages 37 | 38 | 39 | [[dissertations]] 40 | == Dissertations 41 | 42 | * *http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm[Roy Thomas 43 | Fielding - Architectural Styles and the Design of Network-Based Software 44 | Architectures]:* This is the text which defines what REST is. 45 | 46 | 47 | [[books]] 48 | == Books 49 | 50 | * *http://www.amazon.de/REST-Practice-Hypermedia-Systems-Architecture/dp/0596805829[REST in Practice: Hypermedia and Systems Architecture]* 51 | * *https://leanpub.com/build-apis-you-wont-hate[Build APIs You Won't Hate]* 52 | * *http://www.infoq.com/minibooks/emag-web-api[InfoQ eBook - Web APIs: From Start to Finish]* 53 | 54 | 55 | [[blogs]] 56 | == Blogs 57 | 58 | * *http://restful-api-design.readthedocs.org/en/latest/[Lessons-learned blog: Thoughts on RESTful API Design]* 59 | 60 | 61 | -------------------------------------------------------------------------------- /chapters/security.adoc: -------------------------------------------------------------------------------- 1 | [[security]] 2 | = REST Basics - Security 3 | 4 | 5 | [#104] 6 | == {MUST} secure endpoints 7 | 8 | Every API endpoint must be protected and armed with authentication and authorization. 9 | As part of the API definition you must specify how you protect your API using 10 | either the `http` typed `bearer` or `oauth2` typed security schemes defined in the 11 | https://swagger.io/docs/specification/authentication/[OpenAPI Authentication Specification]. 12 | 13 | The majority of our APIs (especially the company internal APIs) are protected 14 | using JWT tokens provided by the platform IAM token service. In these situations 15 | you should use the `http` typed 16 | https://swagger.io/docs/specification/authentication/bearer-authentication/[Bearer Authentication] 17 | security scheme -- it is based on OAuth2.0 {RFC-6750}[RFC 6750] defining the standard header 18 | `Auhorization: Bearer `. 19 | The following code snippet shows how to define the bearer security scheme. 20 | 21 | [source,yaml] 22 | ---- 23 | components: 24 | securitySchemes: 25 | BearerAuth: 26 | type: http 27 | scheme: bearer 28 | bearerFormat: JWT 29 | ---- 30 | 31 | The bearer security schema can then be applied to all API endpoints, e.g. requiring 32 | the token to have `api-repository.read` scope for permission as follows (see 33 | also <<105>>): 34 | 35 | [source,yaml] 36 | ---- 37 | security: 38 | - BearerAuth: [ api-repository.read ] 39 | ---- 40 | 41 | 42 | In other, more specific situations e.g. with customer and partner facing APIs you 43 | may use other OAuth 2.0 authorization flows as defined by {RFC-6749}[RFC 6749]. 44 | Please consult the 45 | https://swagger.io/docs/specification/authentication/oauth2/[OpenAPI OAuth 2.0 Authentication] 46 | section for details on how to define `oauth2` typed security schemes correctly. 47 | 48 | *Note:* Do not use OpenAPI `oauth2` typed security scheme flows (e.g. `implicit`) 49 | if your service does not fully support it and implements a simple bearer token scheme, 50 | because it exposes authentication server address details and may make use of redirection. 51 | 52 | 53 | [#105] 54 | == {MUST} define and assign permissions (scopes) 55 | 56 | Endpoints must be equipped with permissions, if they require client authorization for protection 57 | since e.g. data is exposed that is classified as `orange` or `red` according to Zalando's 58 | https://drive.google.com/file/d/1UPB0UbZP7IvcB52DVWQX41pmB7ugJdAX/view[Data Classification Group Policy (internal link)]. 59 | Please refer to <<225>> for designing permission names. 60 | Some API endpoints may not require specific permissions for authorization e.g. 61 | in case of (i) authorization is _not_ needed for the endpoint since all 62 | exposed data is classified as `green` or `yellow`, 63 | or in case of (ii) the specific authorization is provided differently on 64 | the individual object level. In these situations, however, you must make 65 | it explicit by assigning the `uid` pseudo permission, which is always 66 | available as OAuth2 default scope for all clients in Zalando. 67 | 68 | The defined permissions are assigned to each API endpoint based on the 69 | security schema (see example in <<104, previous section>>) by specifying the 70 | https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#securityRequirementObject[security requirement] 71 | as follows: 72 | 73 | [source,yaml] 74 | ---- 75 | paths: 76 | /business-partners/{partner-id}: 77 | get: 78 | summary: Retrieves information about a business partner 79 | security: 80 | - BearerAuth: [ business-partner-service.read ] 81 | ---- 82 | 83 | *Hint:* Following a minimal API specification approach, the 84 | `Authorization`-header does not need to be defined on each API endpoint, since 85 | it is required and so to say implicitly defined via the security section. 86 | 87 | 88 | [#225] 89 | == {MUST} follow the naming convention for permissions (scopes) 90 | 91 | As long as the <<223,functional naming>> is not yet supported by our permission registry, 92 | permission names in APIs must conform to the following naming pattern: 93 | 94 | [source,bnf] 95 | ----- 96 | ::= | -- should be sufficient for majority of use cases 97 | | -- for special security access differentiation use cases 98 | -- used to explicitly indicate that access is not restricted 99 | 100 | ::= . 101 | ::= .. 102 | ::= uid 103 | 104 | ::= [a-z][a-z0-9-]* -- application identifier 105 | ::= [a-z][a-z0-9-]* -- free resource identifier 106 | ::= read | write -- might be extended in future 107 | ----- 108 | 109 | **Note:** This naming convention only applies to scopes for service-to-service 110 | communication using the Platform IAM tokens. For IAM systems with other naming 111 | rules (e.g. Zalando Partner IAM), the naming should be consistent with the 112 | existing conventions of those systems. 113 | 114 | //// 115 | //Prepared change for functional permission names: 116 | Permission names in APIs must, respectively should conform to the functional 117 | naming depending on the <<219, audience>> as follows (see <<223>> for details 118 | and `` definition): 119 | 120 | [source,bnf] 121 | ----- 122 | ::= | 123 | | -- standard permission without functional naming 124 | | -- resource permission without functional naming 125 | -- used to indicate unrestricted access 126 | 127 | ::= z::[.]. 128 | ::= uid 129 | 130 | ::= [a-z][a-z0-9-]* -- free resource identifier 131 | ::= read || write -- might be extended in future 132 | ----- 133 | 134 | The following application specific legacy convention is *only* allowed for 135 | permissions names of <<223, internal>> APIs: 136 | 137 | [source,bnf] 138 | ----- 139 | ::= . 140 | ::= .. 141 | 142 | ::= [a-z][a-z0-9-]* -- application identifier 143 | 144 | ----- 145 | 146 | //// 147 | 148 | The permission naming schema corresponds to the naming schema for <<224, 149 | hostnames>> and <<213, event type names>>, and typical examples are: 150 | 151 | [cols="25%,20%,15%,40%",options="header",] 152 | |======================================================================= 153 | | Application ID | Resource ID | Access Type | Example 154 | | `order-management` | `sales-order` | `read` | `order-management.sales-order.read` 155 | | `order-management` | `shipment-order` | `read` | `order-management.shipment-order.read` 156 | | `fulfillment-order` | | `write` | `fulfillment-order.write` 157 | | `business-partner-service` | |`read` | `business-partner-service.read` 158 | |======================================================================= 159 | 160 | //// 161 | //Prepared change for functional permission names: 162 | 163 | [cols="15%,15%,15%,15%,40%",options="header",] 164 | |======================================================================= 165 | | Domain | Component | Resource | Access Type | Example 166 | | finance | exchange-rate | - | write | z::finance.exchange-rate.write 167 | | transactions | order | - | read | z::transactions.order.read 168 | | customer | address | shipment-address | read | z::customer.address.shipment-address.read 169 | |======================================================================= 170 | [cols="30%,15%,15%,40%",options="header",] 171 | |======================================================================= 172 | | Application | Resource | Access Type | Example 173 | | business-partner-service | | - | read | z::business-partner-service.read 174 | | order-management | sales-order | write | z::order-management.sales-order.write 175 | |======================================================================= 176 | 177 | //// 178 | 179 | *Note:* APIs should stick to component specific permissions without resource 180 | extension to avoid the complexity of too many fine grained permissions. For the 181 | majority of use cases, restricting access for specific API endpoints using read 182 | or write is sufficient. 183 | -------------------------------------------------------------------------------- /chapters/tooling.adoc: -------------------------------------------------------------------------------- 1 | [[appendix-tooling]] 2 | [appendix] 3 | = Tooling 4 | 5 | This is not a part of the actual guidelines, but might be helpful for following them. 6 | Using a tool mentioned here doesn't automatically ensure you follow the guidelines. 7 | 8 | 9 | [[api-first-integrations]] 10 | == API first integrations 11 | 12 | The following frameworks were specifically designed to support the API First 13 | workflow with OpenAPI YAML files (sorted alphabetically): 14 | 15 | * *https://github.com/zalando/connexion[Connexion]:* 16 | OpenAPI First framework for Python on top of Flask 17 | * *https://github.com/zalando/api-first-hand[Api-First-Hand]:* 18 | API-First Play Bootstrapping Tool for Swagger/OpenAPI specs 19 | * *https://github.com/swagger-api/swagger-codegen[Swagger Codegen]:* 20 | template-driven engine to generate client code in different languages by 21 | parsing Swagger Resource Declaration 22 | * *https://github.com/zalando-stups/swagger-codegen-tooling[Swagger Codegen Tooling]:* 23 | plugin for Maven that generates pieces of code from OpenAPI specification 24 | * *https://github.com/zalando/intellij-swagger[Swagger Plugin for IntelliJ IDEA]:* 25 | plugin to help you easily edit Swagger specification files inside IntelliJ IDEA 26 | 27 | The Swagger/OpenAPI homepage lists more 28 | http://swagger.io/open-source-integrations/[Community-Driven Language 29 | Integrations], but most of them do not fit our API First approach. 30 | 31 | 32 | [[support-libraries]] 33 | == Support libraries 34 | 35 | These utility libraries support you in implementing various parts of our 36 | RESTful API guidelines (sorted alphabetically): 37 | 38 | * *https://github.com/zalando/problem[Problem]:* 39 | Java library that implements application/problem+json 40 | * *https://github.com/zalando/problem-spring-web[Problems for Spring Web MVC]:* 41 | library for handling Problems in Spring Web MVC 42 | * *https://github.com/zalando/jackson-datatype-money[Jackson Datatype Money]:* 43 | extension module to properly support datatypes of javax.money 44 | * *https://github.com/zalando/tracer[Tracer]:* 45 | call tracing and log correlation in distributed systems 46 | -------------------------------------------------------------------------------- /chapters/urls.adoc: -------------------------------------------------------------------------------- 1 | [[urls]] 2 | = REST Basics - URLs 3 | 4 | Guidelines for naming and designing resource paths and query parameters. 5 | 6 | 7 | [#135] 8 | == {SHOULD} not use /api as base path 9 | 10 | In most cases, all resources provided by a service are part of the 11 | public API, and therefore should be made available under the root "/" 12 | base path. 13 | 14 | If the service should also support non-public, internal APIs 15 | — for specific operational support functions, for example — we encourage 16 | you to maintain two different API specifications and provide 17 | <<219, API audience>>. For both APIs, you should not use `/api` as base path. 18 | 19 | We see API's base path as a part of deployment variant configuration. 20 | Therefore, this information has to be declared in the 21 | https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#server-object[server object]. 22 | 23 | 24 | [#134] 25 | == {MUST} pluralize resource names 26 | 27 | Usually, a collection of resource instances is provided (at least the API 28 | should be ready here). The special case of a _resource singleton_ must 29 | be modeled as a collection with cardinality 1 including definition of 30 | `maxItems` = `minItems` = 1 for the returned `array` structure 31 | to make the cardinality constraint explicit. 32 | 33 | **Exception:** the _pseudo identifier_ `self` used to specify a resource endpoint 34 | where the resource identifier is provided by authorization information (see <<143>>). 35 | 36 | 37 | [#228] 38 | == {MUST} use URL-friendly resource identifiers 39 | 40 | To simplify encoding of resource IDs in URLs they must match the regex `[a-zA-Z0-9:._\-/]*`. 41 | Resource IDs only consist of ASCII strings using letters, numbers, underscore, minus, colon, 42 | period, and - on rare occasions - slash. 43 | 44 | **Note:** slashes are only allowed to build and signal resource identifiers 45 | consisting of <<241, compound keys>>. 46 | 47 | **Note:** to prevent ambiguities of <<136, unnormalized paths>> resource 48 | identifiers must never be empty. Consequently, support of empty strings for 49 | path parameters is forbidden. 50 | 51 | 52 | [#129] 53 | == {MUST} use kebab-case for path segments 54 | 55 | Path segments are restricted to ASCII kebab-case strings matching regex `^[a-z][a-z\-0-9]*$`. 56 | The first character must be a lower case letter, and subsequent 57 | characters can be a letter, or a dash(`-`), or a number. 58 | 59 | Example: 60 | 61 | [source,http] 62 | ---- 63 | /shipment-orders/{shipment-order-id} 64 | ---- 65 | 66 | *Hint:* kebab-case applies to concrete path segments and not necessarily the names of path parameters. 67 | 68 | 69 | [#136] 70 | == {MUST} use normalized paths without empty path segments and trailing slashes 71 | 72 | You must not specify paths with duplicate or trailing slashes, e.g. 73 | `/customers//addresses` or `/customers/`. As a consequence, you must also not 74 | specify or use path variables with empty string values. 75 | 76 | *Note:* Non standard paths have no clear semantics. As a result, behavior 77 | for non standard paths varies between different HTTP infrastructure components 78 | and libraries. This may leads to ambiguous and unexpected results during 79 | request handling and monitoring. 80 | 81 | We recommend to implement services robust against clients not following this 82 | rule. All services *should* https://en.wikipedia.org/wiki/URI_normalization[normalize] 83 | request paths before processing by removing duplicate and trailing slashes. 84 | Hence, the following requests should refer to the same resource: 85 | 86 | [source,http] 87 | ---- 88 | GET /orders/{order-id} 89 | GET /orders/{order-id}/ 90 | GET /orders//{order-id} 91 | ---- 92 | 93 | **Note:** path normalization is not supported by all framework out-of-the-box. 94 | Services are required to support at least the normalized path while rejecting 95 | all alternatives paths, if failing to deliver the same resource. 96 | 97 | 98 | [#141] 99 | == {MUST} keep URLs verb-free 100 | 101 | The API describes resources, so the only place where actions should appear is 102 | in the HTTP methods. In URLs, use only nouns. Instead of thinking of actions 103 | (verbs), it's often helpful to think about putting a message in a letter box: 104 | e.g., instead of having the verb _cancel_ in the url, think of sending a 105 | message to cancel an order to the _cancellations_ letter box on the server 106 | side. 107 | 108 | 109 | [#138] 110 | == {MUST} avoid actions — think about resources 111 | 112 | REST is all about your resources, so consider the domain entities that take 113 | part in web service interaction, and aim to model your API around these using 114 | the standard HTTP methods as operation indicators. For instance, if an 115 | application has to lock articles explicitly so that only one user may edit 116 | them, create an article lock with {PUT} or {POST} instead of using a lock 117 | action. 118 | 119 | Request: 120 | 121 | [source,http] 122 | ---- 123 | PUT /article-locks/{article-id} 124 | ---- 125 | 126 | The added benefit is that you already have a service for browsing and filtering 127 | article locks. 128 | 129 | 130 | [#140] 131 | == {SHOULD} define _useful_ resources 132 | 133 | As a rule of thumb resources should be defined to cover 90% of all its client's 134 | use cases. A _useful_ resource should contain as much information as necessary, 135 | but as little as possible. A great way to support the last 10% is to allow 136 | clients to specify their needs for more/less information by supporting 137 | filtering and <<157, embedding>>. 138 | 139 | 140 | [#142] 141 | == {MUST} use domain-specific resource names 142 | 143 | API resources represent elements of the application’s domain model. Using 144 | domain-specific nomenclature for resource names helps developers to understand 145 | the functionality and basic semantics of your resources. It also reduces the 146 | need for further documentation outside the API definition. For example, 147 | "sales-order-items" is superior to "order-items" in that it clearly indicates 148 | which business object it represents. Along these lines, "items" is too general. 149 | 150 | 151 | [#139] 152 | == {SHOULD} model complete business processes 153 | 154 | An API should contain the complete business processes containing all resources 155 | representing the process. This enables clients to understand the business 156 | process, foster a consistent design of the business process, allow for 157 | synergies from description and implementation perspective, and eliminates 158 | implicit invisible dependencies between APIs. 159 | 160 | In addition, it prevents services from being designed as thin wrappers around 161 | databases, which normally tends to shift business logic to the clients. 162 | 163 | 164 | [#143] 165 | == {MUST} identify resources and sub-resources via path segments 166 | 167 | Some API resources may contain or reference sub-resources. Embedded 168 | sub-resources, which are not top-level resources, are parts of a higher-level 169 | resource and cannot be used outside of its scope. Sub-resources should be 170 | referenced by their name and identifier in the path segments as follows: 171 | 172 | [source,http] 173 | ---- 174 | /resources/{resource-id}/sub-resources/{sub-resource-id} 175 | ---- 176 | 177 | In order to improve the consumer experience, you should aim for intuitively 178 | understandable URLs, where each sub-path is a valid reference to a resource or 179 | a set of resources. For instance, if 180 | `/partners/{partner-id}/addresses/{address-id}` is valid, then, in principle, 181 | also `/partners/{partner-id}/addresses`, `/partners/{partner-id}` and 182 | `/partners` must be valid. Examples of concrete url paths: 183 | 184 | [source,http] 185 | ---- 186 | /shopping-carts/de:1681e6b88ec1/items/1 187 | /shopping-carts/de:1681e6b88ec1 188 | /content/images/9cacb4d8 189 | /content/images 190 | ---- 191 | 192 | **Note:** resource identifiers may be build of multiple other resource 193 | identifiers (see <<241>>). 194 | 195 | **Exception:** In some situations the resource identifier is not passed as a 196 | path segment but via the authorization information, e.g. an authorization 197 | token or session cookie. Here, it is reasonable to use **`self`** as 198 | _pseudo-identifier_ path segment. For instance, you may define `/employees/self` 199 | or `/employees/self/personal-details` as resource paths -- and may additionally 200 | define endpoints that support identifier passing in the resource path, like 201 | define `/employees/{empl-id}` or `/employees/{empl-id}/personal-details`. 202 | 203 | 204 | [#241] 205 | == {MAY} expose compound keys as resource identifiers 206 | 207 | If a resource is best identified by a _compound key_ consisting of multiple 208 | other resource identifiers, it is allowed to reuse the compound key in its 209 | natural form containing slashes instead of _technical_ resource identifier in 210 | the resource path without violating the above rule <<143>> as follows: 211 | 212 | [source,http] 213 | ---- 214 | /resources/{compound-key-1}[delim-1]...[delim-n-1]{compound-key-n} 215 | ---- 216 | 217 | Example paths: 218 | 219 | [source,http] 220 | ---- 221 | /shopping-carts/{country}/{session-id} 222 | /shopping-carts/{country}/{session-id}/items/{item-id} 223 | /api-specifications/{docker-image-id}/apis/{path}/{file-name} 224 | /api-specifications/{repository-name}/{artifact-name}:{tag} 225 | /article-size-advices/{sku}/{sales-channel} 226 | ---- 227 | 228 | *Note*: Exposing a compound key as described above limits ability to 229 | evolve the structure of the resource identifier as it is no longer opaque. 230 | 231 | To compensate for this drawback, APIs must apply a compound key abstraction 232 | consistently in all requests and responses parameters and attributes allowing 233 | consumers to treat these as _technical resource identifier_ replacement. The 234 | use of independent compound key components must be limited to search and 235 | creation requests, as follows: 236 | 237 | [source,http] 238 | ---- 239 | # compound key components passed as independent search query parameters 240 | GET /article-size-advices?skus=sku-1,sku-2&sales_channel_id=sid-1 241 | => { "items": [{ "id": "id-1", ... },{ "id": "id-2", ... }] } 242 | 243 | # opaque technical resource identifier passed as path parameter 244 | GET /article-size-advices/id-1 245 | => { "id": "id-1", "sku": "sku-1", "sales_channel_id": "sid-1", "size": ... } 246 | 247 | # compound key components passed as mandatory request fields 248 | POST /article-size-advices { "sku": "sku-1", "sales_channel_id": "sid-1", "size": ... } 249 | => { "id": "id-1", "sku": "sku-1", "sales_channel_id": "sid-1", "size": ... } 250 | ---- 251 | 252 | Where `id-1` is representing the opaque provision of the compound key 253 | `sku-1/sid-1` as technical resource identifier. 254 | 255 | **Remark:** A compound key component may itself be used as another resource 256 | identifier providing another resource endpoint, e.g `/article-size-advices/{sku}`. 257 | 258 | 259 | [#145] 260 | == {MAY} consider using (non-) nested URLs 261 | 262 | If a sub-resource is only accessible via its parent resource and may not exist 263 | without parent resource, consider using a nested URL structure, for instance: 264 | 265 | [source,http] 266 | ---- 267 | /shoping-carts/de/1681e6b88ec1/cart-items/1 268 | ---- 269 | 270 | However, if the resource can be accessed directly via its unique id, then the 271 | API should expose it as a top-level resource. For example, customer has a 272 | collection for sales orders; however, sales orders have globally unique id and 273 | some services may choose to access the orders directly, for instance: 274 | 275 | [source,http] 276 | ---- 277 | /customers/1637asikzec1 278 | /sales-orders/5273gh3k525a 279 | ---- 280 | 281 | 282 | [#146] 283 | == {SHOULD} limit number of resource types 284 | 285 | To keep maintenance and service evolution manageable, we should follow 286 | "functional segmentation" and "separation of concern" design principles and do 287 | not mix different business functionalities in same API definition. In practice 288 | this means that the number of resource types exposed via an API should be 289 | limited. In this context a resource type is defined as a set of highly related 290 | resources such as a collection, its members and any direct sub-resources. 291 | 292 | For example, the resources below would be counted as three resource types, one 293 | for customers, one for the addresses, and one for the customers' related 294 | addresses: 295 | 296 | [source,http] 297 | ---- 298 | /customers 299 | /customers/{id} 300 | /customers/{id}/preferences 301 | /customers/{id}/addresses 302 | /customers/{id}/addresses/{addr} 303 | /addresses 304 | /addresses/{addr} 305 | ---- 306 | 307 | Note that: 308 | 309 | * We consider `/customers/{id}/preferences` part of the `/customers` resource 310 | type because it has a one-to-one relation to the customer without an 311 | additional identifier. 312 | * We consider `/customers` and `/customers/{id}/addresses` as separate resource 313 | types because `/customers/{id}/addresses/{addr}` also exists with an 314 | additional identifier for the address. 315 | * We consider `/addresses` and `/customers/{id}/addresses` as separate resource 316 | types because there's no reliable way to be sure they are the same. 317 | 318 | Given this definition, our experience is that well defined APIs involve no more 319 | than 4 to 8 resource types. There may be exceptions with more complex business 320 | domains that require more resources, but you should first check if you can 321 | split them into separate subdomains with distinct APIs. 322 | 323 | Nevertheless one API should hold all necessary resources to model complete 324 | business processes helping clients to understand these flows. 325 | 326 | 327 | [#147] 328 | == {SHOULD} limit number of sub-resource levels 329 | 330 | There are main resources (with root url paths) and sub-resources (or _nested_ 331 | resources with non-root urls paths). Use sub-resources if their life cycle is 332 | (loosely) coupled to the main resource, i.e. the main resource works as 333 | collection resource of the subresource entities. You should use <= 3 334 | sub-resource (nesting) levels -- more levels increase API complexity and url 335 | path length. (Remember, some popular web browsers do not support URLs of more 336 | than 2000 characters.) 337 | 338 | 339 | [#130] 340 | == {MUST} use snake_case (never camelCase) for query parameters 341 | 342 | See also <<118>>. 343 | 344 | [#137] 345 | == {MUST} stick to conventional query parameters 346 | 347 | If you provide query support for searching, sorting, filtering, and 348 | paginating, you must stick to the following naming conventions: 349 | 350 | * [[q]]{q}: default query parameter, e.g. used by browser tab completion; 351 | should have an entity specific alias, e.g. sku. 352 | * [[sort]]{sort}: comma-separated list of fields (as defined by <<154>>) to 353 | define the sort order. To indicate sorting direction, fields may be prefixed 354 | with `+` (ascending) or `-` (descending), e.g. /sales-orders?sort=+id. 355 | * [[fields]]{fields}: field name expression to retrieve only a subset of fields 356 | of a resource. See <<157>> below. 357 | * [[embed]]{embed}: field name expression to expand or embedded sub-entities, 358 | e.g. inside of an article entity, expand silhouette code into the silhouette 359 | object. Implementing {embed} correctly is difficult, so do it with care. 360 | See <<158>> below. 361 | * [[offset]]{offset}: numeric offset of the first element provided on a page 362 | representing a collection request. See <> section below. 363 | * [[cursor]]{cursor}: an opaque pointer to a page, never to be inspected or 364 | constructed by clients. It usually (encrypted) encodes the page position, 365 | i.e. the identifier of the first or last page element, the pagination 366 | direction, and the applied query filters to recreate the collection. See 367 | <> or <> section below. 368 | * [[limit]]{limit}: client suggested limit to restrict the number of entries on 369 | a page. See <> section below. 370 | -------------------------------------------------------------------------------- /models/money-1.0.0.yaml: -------------------------------------------------------------------------------- 1 | Money: 2 | type: object 3 | properties: 4 | amount: 5 | type: number 6 | description: > 7 | The amount describes unit and subunit of the currency in a single value, 8 | where the integer part (digits before the decimal point) is for the 9 | major unit and fractional part (digits after the decimal point) is for 10 | the minor unit. 11 | format: decimal 12 | example: 99.95 13 | currency: 14 | type: string 15 | description: 3 letter currency code as defined by ISO-4217 16 | format: iso-4217 17 | example: EUR 18 | required: 19 | - amount 20 | - currency 21 | -------------------------------------------------------------------------------- /models/problem-1.0.0.yaml: -------------------------------------------------------------------------------- 1 | Problem: 2 | type: object 3 | properties: 4 | type: 5 | type: string 6 | format: uri 7 | description: | 8 | An absolute URI that identifies the problem type. When dereferenced, 9 | it SHOULD provide human-readable documentation for the problem type 10 | (e.g., using HTML). 11 | default: 'about:blank' 12 | example: 'https://zalando.github.io/problem/constraint-violation' 13 | title: 14 | type: string 15 | description: | 16 | A short, summary of the problem type. Written in english and readable 17 | for engineers (usually not suited for non technical stakeholders and 18 | not localized); example: Service Unavailable 19 | status: 20 | type: integer 21 | format: int32 22 | description: | 23 | The HTTP status code generated by the origin server for this occurrence 24 | of the problem. 25 | minimum: 100 26 | maximum: 600 27 | exclusiveMaximum: true 28 | example: 503 29 | detail: 30 | type: string 31 | description: | 32 | A human readable explanation specific to this occurrence of the 33 | problem. 34 | example: Connection to database timed out 35 | instance: 36 | type: string 37 | format: uri 38 | description: | 39 | An absolute URI that identifies the specific occurrence of the problem. 40 | It may or may not yield further information if dereferenced. 41 | -------------------------------------------------------------------------------- /models/problem-1.0.1.yaml: -------------------------------------------------------------------------------- 1 | Problem: 2 | type: object 3 | properties: 4 | type: 5 | type: string 6 | format: uri-reference 7 | description: > 8 | A URI reference that uniquely identifies the problem type only in the 9 | context of the provided API. Opposed to the specification in RFC-9457, 10 | it is neither recommended to be dereferenceable and point to a 11 | human-readable documentation nor globally unique for the problem type. 12 | default: 'about:blank' 13 | example: '/some/uri-reference' 14 | title: 15 | type: string 16 | description: > 17 | A short summary of the problem type. Written in English and readable 18 | for engineers, usually not suited for non technical stakeholders and 19 | not localized. 20 | example: some title for the error situation 21 | status: 22 | type: integer 23 | format: int32 24 | description: > 25 | The HTTP status code generated by the origin server for this occurrence 26 | of the problem. 27 | minimum: 100 28 | maximum: 600 29 | exclusiveMaximum: true 30 | detail: 31 | type: string 32 | description: > 33 | A human readable explanation specific to this occurrence of the 34 | problem that is helpful to locate the problem and give advice on how 35 | to proceed. Written in English and readable for engineers, usually not 36 | suited for non technical stakeholders and not localized. 37 | example: some description for the error situation 38 | instance: 39 | type: string 40 | format: uri-reference 41 | description: > 42 | A URI reference that identifies the specific occurrence of the problem, 43 | e.g. by adding a fragment identifier or sub-path to the problem type. 44 | May be used to locate the root of this problem in the source code. 45 | example: '/some/uri-reference#specific-occurrence-context' 46 | -------------------------------------------------------------------------------- /models/request-headers-1.0.0.yaml: -------------------------------------------------------------------------------- 1 | # This document is DEPRECATED and only maintained for backward compatibility. 2 | # Separation of request vs. response header definition is partially redundant and not needed. 3 | # Instead, please use the HEADER DEFINITIONS provided in headers-.yaml 4 | 5 | # Standard Headers 6 | 7 | If-Match: 8 | name: If-Match 9 | in: header 10 | required: false 11 | description: |- 12 | The `If-Match` header field is used to declare a list of identifiers that 13 | are required to match the current resource version identifier in at least 14 | one position as a pre-condition for executing the request on the server 15 | side. This behavior is used to validate and reject optimistic updates, by 16 | checking if the resource version a consumer has based his changes on is 17 | outdated on arrival of the change request to prevent lost updates. 18 | 19 | If the pre-condition fails the server will respond with status code `412` 20 | (Precondition Failed). For further details see [RFC 9110 Section 21 | 13.1.1. If-Match](https://datatracker.ietf.org/doc/html/rfc9110#name-if-match) 22 | and [RFC 9111 Section 4.3.1 Sending a Validation Request](https://datatracker.ietf.org/doc/html/rfc9111#name-sending-a-validation-reques). 23 | schema: 24 | type: array 25 | items: 26 | type: string 27 | style: simple 28 | explode: false 29 | example: [ "5db68c06-1a68-11e9-8341-68f728c1ba70", 'W/"xy", "5"' ] 30 | 31 | If-None-Match: 32 | name: If-None-Match 33 | in: header 34 | required: false 35 | description: |- 36 | The `If-None-Match header` field is used to declare a list of identifiers 37 | that are required to fail matching all the current resource version 38 | identifiers as a pre-condition for executing the request on the server 39 | side. This is especially used in conjunction with an `*` (asterix) that is 40 | matching all possible resource identifiers to ensure the initial creation 41 | of a resource. Other use cases are possible but rare. 42 | 43 | If the pre-condition fails the server will respond with status code `412` 44 | (Precondition Failed). For further details see [RFC 9110 Section 45 | 13.1.2](https://datatracker.ietf.org/doc/html/rfc9110#name-if-none-match) 46 | and [RFC 9111 Section 4.3.1 Sending a Validation Request](https://datatracker.ietf.org/doc/html/rfc9111#name-sending-a-validation-reques). 47 | schema: 48 | type: array 49 | items: 50 | type: string 51 | style: simple 52 | explode: false 53 | example: [ "5db68c06-1a68-11e9-8341-68f728c1ba70", 'W/"xy", "5"' ] 54 | 55 | # Custom Headers 56 | 57 | X-Flow-ID: 58 | name: X-Flow-ID 59 | description: |- 60 | The `X-Flow-ID` is a custom header containing a unique flow identifier that 61 | that must be passed to any further request. It can be used to investigate 62 | request related log entries end events. 63 | in: header 64 | schema: 65 | type: string 66 | example: GKY7oDhpSiKY_gAAAABZ_A 67 | 68 | X-Mobile-Advertising-ID: 69 | in: header 70 | name: X-Mobile-Advertising-ID 71 | description: |- 72 | A unique, customer-resettable identifier provided by the operating system of 73 | a mobile device to facilitate personalized advertising, and usually passed 74 | by mobile apps via header when calling backend services. 75 | 76 | It is either the [IDFA](https://developer.apple.com/documentation/adsupport/asidentifiermanager) 77 | (Apple Identifier for mobile Advertising) for iOS, or the 78 | [GAID](https://support.google.com/googleplay/android-developer/answer/6048248) 79 | (Google mobile Advertising Identifier) for Android. 80 | schema: 81 | type: string 82 | example: cdda802e-fb9c-47ad-0794d394c912 83 | 84 | X-Tenant-ID: 85 | name: X-Tenant-ID 86 | description: |- 87 | The unique identifier of the tenant that initiated the request to the 88 | multi-tenant Zalando Platform. The X-Tenant-ID is set to the ID of the 89 | business partner extracted from the `OAuth`-token of the request. The 90 | `X-Tenant-ID` should be passed-through as generic aspect. 91 | in: header 92 | schema: 93 | type: string 94 | example: 9f8b3ca3-4be5-436c-a847-9cd55460c495 95 | 96 | X-Sales-Channel: 97 | name: X-Sales-Channel 98 | description: |- 99 | Unique identifier of the sales channel related to the request. Sales 100 | channels are owned by retailers and represent a specific consumer segment 101 | being addressed with a specific product assortment that is offered via the 102 | CFA retailer catalogs to consumers (see fashion platform glossary). The 103 | `X-Sales-Channel` should be passed-through as generic aspect. 104 | in: header 105 | schema: 106 | type: string 107 | example: 52b96501-0f8d-43e7-82aa-8a96fab134d7 108 | 109 | X-Frontend-Type: 110 | name: X-Frontend-Type 111 | description: |- 112 | The type of the consumer facing application (CFA) used to initiate the 113 | request. CFAs provide business experience to their customers via different 114 | frontend application types, `mobile-app`, `browser`, `facebook-app`, 115 | `chat-app`, and `email`. The `X-Frontend-Type` should be passed-through as 116 | generic aspect - there are diverse concerns, e.g. pushing mobiles with 117 | specific coupons, that make use of it. 118 | in: header 119 | schema: 120 | type: string 121 | example: mobile-app 122 | 123 | X-device-Type: 124 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/request-headers-1.0.0.yaml#/X-Device-Type' 125 | 126 | X-Device-Type: 127 | name: X-Device-Type 128 | description: |- 129 | The device type used to initiate the request. There are also use cases 130 | for steering customer experience (incl. features and content) depending on 131 | device types, e.g. `smartphone`, `tablet`, `desktop`, and `other`. The 132 | `X-Device-Type` should be passed-through as generic aspect. 133 | in: header 134 | schema: 135 | type: string 136 | example: tablet 137 | 138 | X-device-OS: 139 | $ref: 'https://opensource.zalando.com/restful-api-guidelines/models/request-headers-1.0.0.yaml#/X-Device-OS' 140 | 141 | X-Device-OS: 142 | name: X-Device-OS 143 | description: |- 144 | The operating system used by the device initiating the request. On top of 145 | device type, we want to differ between device platforms, e.g. between 146 | computers, smartphone and tablets with `iOS`, `Android`, `Windows`, 147 | `Linux`, and `MacOS`. The `X-Device-OS` should be passed-through as generic 148 | aspect. 149 | in: header 150 | schema: 151 | type: string 152 | example: Android 153 | -------------------------------------------------------------------------------- /models/response-headers-1.0.0.yaml: -------------------------------------------------------------------------------- 1 | # This document is DEPRECATED and only maintained for backward compatibility. 2 | # Separation of request vs. response header definition is partially redundant and not needed. 3 | # Instead, please use the HEADER DEFINITIONS provided in headers-.yaml 4 | 5 | # Standard Headers 6 | 7 | Cache-Control: 8 | in: header 9 | name: Cache-Control 10 | description: | 11 | The `Cache-Control` header field is providing directives to control how 12 | proxies and clients are allowed to cache responses results for performance. 13 | Clients and proxies are free to not support caching of results, however if 14 | they do, they must obey all directives mentioned in [RFC-9111 Section 15 | 5.2.2](https://datatracker.ietf.org/doc/html/rfc9111#name-response-directives) to the word. 16 | 17 | In case of caching, the directive provides the scope of the cache entry, 18 | i.e. only for the original user (private) or shared between all users 19 | (public), the lifetime of the cache entry in seconds (max-age), and the 20 | strategy how to handle a stale cache entry (must-revalidate). Please note, 21 | that the lifetime and validation directives for shared caches are different 22 | (s-maxage, proxy-revalidate). 23 | schema: 24 | type: string 25 | example: "private,must-revalidate,max-age=3600" 26 | 27 | Vary: 28 | in: header 29 | name: Vary 30 | description: | 31 | The `Vary` header field in the response describes which parts of the 32 | request message, aside from the method, the `Host` header field, and the 33 | request target path, might have influence the server in selecting the 34 | presented response. A client or proxy that caches the response must respect 35 | this information to ensure that it delivers the correct cache entry (see 36 | [RFC 9110 Section 12.5.5. Vary](https://datatracker.ietf.org/doc/html/rfc9110#name-vary) 37 | and [RFC 9111 Section 4.1 Calculating Cache Keys with the Vary Header Field](https://datatracker.ietf.org/doc/html/rfc9111#name-calculating-cache-keys-with)). 38 | 39 | The value consists of either a single asterisk (`*`) or a list of 40 | case-insensitive header field names. 41 | schema: 42 | type: string 43 | example: "accept-encoding,accept-language" 44 | 45 | ETag: 46 | in: header 47 | name: ETag 48 | description: > 49 | The `ETag` header field in a response provides an opaque quoted string 50 | identifying the distinct delivered resource. The same selected resource 51 | depending on version and representation may be identified by multiple 52 | identifiers. The `ETag` value is guaranteed to change whenever the 53 | resource changes, and thereby enabling optimistic updates. 54 | 55 | An identifier consists of an opaque quoted string, possibly prefixed by 56 | a weakness indicator. For further details see [RFC 9110 Section 57 | 8.8.3. ETag](https://datatracker.ietf.org/doc/html/rfc9110#name-etag) and 58 | [RFC 9111 Section 4.3.1 Sending a Validation Request](https://datatracker.ietf.org/doc/html/rfc9111#name-sending-a-validation-reques) 59 | schema: 60 | type: string 61 | example: 'W/"xy", "5", "5db68c06-1a68-11e9-8341-68f728c1ba70"'' 62 | 63 | # Custom Headers 64 | 65 | X-Flow-ID: 66 | name: X-Flow-ID 67 | description: | 68 | The `X-Flow-ID` is a custom header containing a unique flow identifier that 69 | was be passed to any further request. It can be used to investigate request 70 | related log entries end events. 71 | in: header 72 | schema: 73 | type: string 74 | example: GKY7oDhpSiKY_gAAAABZ_A 75 | -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/NotoEmoji-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Emoji/NotoEmoji-Bold.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/NotoEmoji-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Emoji/NotoEmoji-Light.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/NotoEmoji-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Emoji/NotoEmoji-Medium.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/NotoEmoji-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Emoji/NotoEmoji-Regular.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/NotoEmoji-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Emoji/NotoEmoji-SemiBold.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/NotoEmoji-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Emoji/NotoEmoji-VariableFont_wght.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/OFL.txt: -------------------------------------------------------------------------------- 1 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 2 | This license is copied below, and is also available with a FAQ at: 3 | http://scripts.sil.org/OFL 4 | 5 | 6 | ----------------------------------------------------------- 7 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 8 | ----------------------------------------------------------- 9 | 10 | PREAMBLE 11 | The goals of the Open Font License (OFL) are to stimulate worldwide 12 | development of collaborative font projects, to support the font creation 13 | efforts of academic and linguistic communities, and to provide a free and 14 | open framework in which fonts may be shared and improved in partnership 15 | with others. 16 | 17 | The OFL allows the licensed fonts to be used, studied, modified and 18 | redistributed freely as long as they are not sold by themselves. The 19 | fonts, including any derivative works, can be bundled, embedded, 20 | redistributed and/or sold with any software provided that any reserved 21 | names are not used by derivative works. The fonts and derivatives, 22 | however, cannot be released under any other type of license. The 23 | requirement for fonts to remain under this license does not apply 24 | to any document created using the fonts or their derivatives. 25 | 26 | DEFINITIONS 27 | "Font Software" refers to the set of files released by the Copyright 28 | Holder(s) under this license and clearly marked as such. This may 29 | include source files, build scripts and documentation. 30 | 31 | "Reserved Font Name" refers to any names specified as such after the 32 | copyright statement(s). 33 | 34 | "Original Version" refers to the collection of Font Software components as 35 | distributed by the Copyright Holder(s). 36 | 37 | "Modified Version" refers to any derivative made by adding to, deleting, 38 | or substituting -- in part or in whole -- any of the components of the 39 | Original Version, by changing formats or by porting the Font Software to a 40 | new environment. 41 | 42 | "Author" refers to any designer, engineer, programmer, technical 43 | writer or other person who contributed to the Font Software. 44 | 45 | PERMISSION & CONDITIONS 46 | Permission is hereby granted, free of charge, to any person obtaining 47 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 48 | redistribute, and sell modified and unmodified copies of the Font 49 | Software, subject to the following conditions: 50 | 51 | 1) Neither the Font Software nor any of its individual components, 52 | in Original or Modified Versions, may be sold by itself. 53 | 54 | 2) Original or Modified Versions of the Font Software may be bundled, 55 | redistributed and/or sold with any software, provided that each copy 56 | contains the above copyright notice and this license. These can be 57 | included either as stand-alone text files, human-readable headers or 58 | in the appropriate machine-readable metadata fields within text or 59 | binary files as long as those fields can be easily viewed by the user. 60 | 61 | 3) No Modified Version of the Font Software may use the Reserved Font 62 | Name(s) unless explicit written permission is granted by the corresponding 63 | Copyright Holder. This restriction only applies to the primary font name as 64 | presented to the users. 65 | 66 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 67 | Software shall not be used to promote, endorse or advertise any 68 | Modified Version, except to acknowledge the contribution(s) of the 69 | Copyright Holder(s) and the Author(s) or with their explicit written 70 | permission. 71 | 72 | 5) The Font Software, modified or unmodified, in part or in whole, 73 | must be distributed entirely under this license, and must not be 74 | distributed under any other license. The requirement for fonts to 75 | remain under this license does not apply to any document created 76 | using the Font Software. 77 | 78 | TERMINATION 79 | This license becomes null and void if any of the above conditions are 80 | not met. 81 | 82 | DISCLAIMER 83 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 84 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 85 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 86 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 87 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 88 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 89 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 90 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 91 | OTHER DEALINGS IN THE FONT SOFTWARE. 92 | -------------------------------------------------------------------------------- /resources/fonts/Noto_Emoji/README.txt: -------------------------------------------------------------------------------- 1 | Noto Emoji Variable Font 2 | ======================== 3 | 4 | This download contains Noto Emoji as both a variable font and static fonts. 5 | 6 | Noto Emoji is a variable font with this axis: 7 | wght 8 | 9 | This means all the styles are contained in a single file: 10 | NotoEmoji-VariableFont_wght.ttf 11 | 12 | If your app fully supports variable fonts, you can now pick intermediate styles 13 | that aren’t available as static fonts. Not all apps support variable fonts, and 14 | in those cases you can use the static font files for Noto Emoji: 15 | static/NotoEmoji-Light.ttf 16 | static/NotoEmoji-Regular.ttf 17 | static/NotoEmoji-Medium.ttf 18 | static/NotoEmoji-SemiBold.ttf 19 | static/NotoEmoji-Bold.ttf 20 | 21 | Get started 22 | ----------- 23 | 24 | 1. Install the font files you want to use 25 | 26 | 2. Use your app's font picker to view the font family and all the 27 | available styles 28 | 29 | Learn more about variable fonts 30 | ------------------------------- 31 | 32 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 33 | https://variablefonts.typenetwork.com 34 | https://medium.com/variable-fonts 35 | 36 | In desktop apps 37 | 38 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 39 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 40 | 41 | Online 42 | 43 | https://developers.google.com/fonts/docs/getting_started 44 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 45 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 46 | 47 | Installing fonts 48 | 49 | MacOS: https://support.apple.com/en-us/HT201749 50 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 51 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 52 | 53 | Android Apps 54 | 55 | https://developers.google.com/fonts/docs/android 56 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 57 | 58 | License 59 | ------- 60 | Please read the full license text (OFL.txt) to understand the permissions, 61 | restrictions and requirements for usage, redistribution, and modification. 62 | 63 | You can use them in your products & projects – print or digital, 64 | commercial or otherwise. 65 | 66 | This isn't legal advice, please consider consulting a lawyer and see the full 67 | license for all details. 68 | -------------------------------------------------------------------------------- /resources/fonts/Noto_Serif/NotoSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Serif/NotoSerif-Bold.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Serif/NotoSerif-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Serif/NotoSerif-BoldItalic.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Serif/NotoSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Serif/NotoSerif-Italic.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Serif/NotoSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Noto_Serif/NotoSerif-Regular.ttf -------------------------------------------------------------------------------- /resources/fonts/Noto_Serif/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 Google Inc. All Rights Reserved. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-Bold.ttf -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-BoldItalic.ttf -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-Italic.ttf -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-Light.ttf -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-LightItalic.ttf -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-Medium.ttf -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-MediumItalic.ttf -------------------------------------------------------------------------------- /resources/fonts/Ubuntu/Ubuntu-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/restful-api-guidelines/4822efbe29508fe1993ab104163f37b2a1303978/resources/fonts/Ubuntu/Ubuntu-Regular.ttf -------------------------------------------------------------------------------- /resources/themes/pdf-theme.yml: -------------------------------------------------------------------------------- 1 | extends: base 2 | base: 3 | font-family: NotoSerif 4 | font: 5 | catalog: 6 | merge: false 7 | Ubuntu: 8 | normal: Ubuntu/Ubuntu-Regular.ttf 9 | italic: Ubuntu/Ubuntu-Italic.ttf 10 | bold: Ubuntu/Ubuntu-Bold.ttf 11 | bold_italic: Ubuntu/Ubuntu-BoldItalic.ttf 12 | NotoSerif: 13 | normal: Noto_Serif/NotoSerif-Regular.ttf 14 | italic: Noto_Serif/NotoSerif-Italic.ttf 15 | bold: Noto_Serif/NotoSerif-Bold.ttf 16 | bold_italic: Noto_Serif/NotoSerif-BoldItalic.ttf 17 | Noto_Emoji: 18 | normal: Noto_Emoji/NotoEmoji-Regular.ttf 19 | italic: Noto_Emoji/NotoEmoji-Regular.ttf 20 | bold: Noto_Emoji/NotoEmoji-Bold.ttf 21 | bold_italic: Noto_Emoji/NotoEmoji-Bold.ttf 22 | fallbacks: 23 | - Noto_Emoji 24 | -------------------------------------------------------------------------------- /sass/settings/_zalando.scss: -------------------------------------------------------------------------------- 1 | $header-font-color: #404040; 2 | 3 | $must-color: red; 4 | $should-color: darkgoldenrod; 5 | $may-color: green; 6 | 7 | @media screen and (max-width: 768px) { 8 | #toc.toc2 { 9 | display: none; 10 | } 11 | } -------------------------------------------------------------------------------- /sass/zalando.scss: -------------------------------------------------------------------------------- 1 | @import "foundation"; 2 | @import "settings/zalando"; 3 | @import "components/asciidoc"; 4 | @import "components/awesome-icons"; 5 | 6 | .must-keyword { 7 | color: $must-color; 8 | } 9 | 10 | .should-keyword { 11 | color: $should-color; 12 | } 13 | 14 | .may-keyword { 15 | color: $may-color; 16 | } 17 | 18 | .status-code, .status-code:link, .status-code:visited, .status-code:hover, .status-code:active { 19 | color: black; 20 | font-weight: bold; 21 | } 22 | 23 | /* HTTP Status Codes custom text span syntax / roles / highlight */ 24 | .status-code-hint { 25 | border-radius: 4px; 26 | border-width: 2px; 27 | border-style: solid; 28 | padding: 4px 2px 0px 2px; 29 | font-size: 0.8em; 30 | } 31 | 32 | .status-code-hint.mustnot { 33 | color: white; 34 | background-color: red; 35 | border-color: red; 36 | } 37 | 38 | .status-code-hint.should { 39 | color: white; 40 | background-color: rgb(50, 192, 50); 41 | border-color: rgb(50, 192, 50); 42 | } 43 | 44 | .status-code-hint.shouldnot { 45 | color: #595959; 46 | background-color: rgb(229, 232, 45); 47 | border-color: rgb(229, 232, 45); 48 | } 49 | 50 | .status-code-hint.may { 51 | color: white; 52 | background-color: rgb(105, 105, 245); 53 | border-color: rgb(105, 105, 245); 54 | } 55 | 56 | .status-code-methods { 57 | border-radius: 4px; 58 | border-width: 2px; 59 | border-style: solid; 60 | padding: 4px 2px 0px 2px; 61 | font-size: 0.8em; 62 | } 63 | 64 | .do-not-use { 65 | color: gray; 66 | } 67 | 68 | .do-not-use h5 > a { 69 | color: gray; 70 | } 71 | 72 | .indent { 73 | padding-left: 4rem; 74 | } 75 | 76 | .sect1 { 77 | max-width: 45em; 78 | } 79 | 80 | #content #toc { 81 | max-width: 45em; 82 | } 83 | 84 | code { 85 | font-size: .85em !important; 86 | } 87 | 88 | p > code { 89 | background-color: #f0f0f0; 90 | padding: .1em .4em .1em .4em !important; 91 | font-size: .9em !important; 92 | color: inherit; 93 | } 94 | 95 | a > code { 96 | background-color: #f0f0f0; 97 | padding: .1em .4em .1em .4em !important; 98 | font-size: .9em !important; 99 | color: black; 100 | } 101 | 102 | .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { 103 | padding: .5em !important; 104 | } 105 | 106 | #toc a { 107 | color: #364149; 108 | } 109 | -------------------------------------------------------------------------------- /scripts/build-css.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Script to generate CSS for the Guidelines 3 | 4 | set -ex #print commands, exit on failure 5 | 6 | pushd "$(dirname "${0}")" > /dev/null 7 | GUIDELINES_DIR="$(pwd)" 8 | popd > /dev/null 9 | 10 | STYLESHEET_FACTORY_DIR="$(mktemp -d)" 11 | mkdir -p "${STYLESHEET_FACTORY_DIR}" 12 | 13 | git clone git@github.com:asciidoctor/asciidoctor-stylesheet-factory.git "${STYLESHEET_FACTORY_DIR}" 14 | cd "${STYLESHEET_FACTORY_DIR}" && bundle install 15 | 16 | cp -r "${GUIDELINES_DIR}/sass/"* "${STYLESHEET_FACTORY_DIR}/sass" 17 | cd "${STYLESHEET_FACTORY_DIR}" && compass compile 18 | cp "${STYLESHEET_FACTORY_DIR}/stylesheets/zalando.css" "${GUIDELINES_DIR}/" -------------------------------------------------------------------------------- /scripts/create-zally-issue.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -ex 4 | 5 | #GH_REPO="github.com/zalando/restful-api-guidelines.git" 6 | GH_REPO_URL="https://api.github.com/repos/zalando/restful-api-guidelines" 7 | ZALLY_REPO_URL="https://api.github.com/repos/zalando/zally" 8 | 9 | create_zally_issue () { 10 | local commit message count=0; 11 | while [ "${count}" -lt 6 ]; do 12 | commit=$(curl -s "${GH_REPO_URL}/commits/${GITHUB_SHA}"); 13 | message="$(echo "${commit}" | jq --raw-output '.message' || true)"; 14 | if [ "${message}" != "No commit found for SHA: ${GITHUB_SHA}" ]; then 15 | break; 16 | fi; 17 | count=$((count + 1)); sleep 10; 18 | done; 19 | 20 | local files=($(echo "${commit}" | jq --raw-output '.files[].filename' || true)) 21 | local chapters=false; 22 | for file in "${files[@]}"; do 23 | if [[ ${file} == chapters/* ]]; then chapters=true; break; fi; 24 | done; 25 | if [ ${chapters} = false ]; then 26 | echo "No changes, aborting issue creation (${GITHUB_SHA}}"; return; 27 | fi; 28 | 29 | local pull origin count=0; 30 | while [ "${count}" -lt 6 ]; do 31 | pull="$(curl -s "${GH_REPO_URL}/commits/${GITHUB_SHA}/pulls" | jq '.[0]')"; 32 | origin="$(echo "${pull}" | jq --raw-output '.number')"; 33 | if [ "${origin}" != "null" ]; then break; fi; 34 | count=$((count + 1)); sleep 10; 35 | done; 36 | 37 | if [ "${origin}" == "null" ]; then 38 | origin="$(echo "${commit}" | \ 39 | jq --raw-output '.commit.message | capture("#(?[0-9]+)( |$)") | .n')"; 40 | pull="$(curl -s ${GH_REPO_URL}/pulls/${origin})"; 41 | fi; 42 | 43 | local title url body; 44 | if [ "${origin}" != "null" ]; then 45 | title="$(echo "${pull}" | jq --raw-output '.title' | \ 46 | sed 's/"/'"'"'/g' || true)"; 47 | url="$(echo "${pull}" | jq --raw-output '.html_url' )"; 48 | body="Please check if the PR ${url} introduces changes which are relevant to the Zally project."; 49 | else 50 | title="$(echo "${commit}" | jq --raw-output '.commit.message' | \ 51 | sed 's/"/'"'"'/g' | head -n 1)"; 52 | url="$(echo "${commit}" | jq --raw-output '.html_url')" 53 | body="Please check if the COMMIT ${url} introduces changes which are relevant to the Zally project."; 54 | fi; 55 | local data="{\"title\":\"${title}\", \"body\": \"${body}\", \"labels\": [\"guidelines-update\"]}"; 56 | 57 | echo "Changes require zally issue creation (${GITHUB_SHA}}"; 58 | curl -X POST --data "${data}" \ 59 | -H 'Content-Type: application/json' \ 60 | -H "Authorization: token ${GH_TOKEN}" \ 61 | "${ZALLY_REPO_URL}/issues"; 62 | } 63 | 64 | create_zally_issue 65 | 66 | -------------------------------------------------------------------------------- /scripts/generate-includes.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | readonly DIR="${1}"; 4 | readonly SOURCES=("models/headers-1.0.0.yaml"); 5 | readonly HEADERS=($(cat "${SOURCES[@]}" | awk ' 6 | BEGIN { mode = "parameters" } 7 | ($0 ~ "^#.*response headers") { 8 | mode = "headers"; next 9 | } 10 | ($0 ~ "^#.*request headers$") { 11 | mode = "parameters"; next 12 | } 13 | ($0 ~ "^#.*common headers$") { 14 | mode = "parameters"; next 15 | } 16 | ($0 ~ "^[A-Z][A-Za-z-]*:$") { 17 | print $0 mode 18 | }')) 19 | 20 | for HEADER in "${HEADERS[@]}"; do 21 | NAME="${HEADER%%:*}"; MODE="${HEADER##*:}"; 22 | if [ -n "${DIR}" ]; then 23 | FILE="${DIR}/$(echo ${NAME} | tr '[A-Z]' '[a-z]').yaml"; 24 | else FILE="/dev/stdout"; fi; 25 | awk -v name="${NAME}" -v mode="${MODE}" ' 26 | (($0 ~ "^[A-Z][A-Za-z-]*:$" || $0 ~ "^#.*$") && content) { 27 | print content; content = "" 28 | } 29 | 30 | (content) { 31 | if (mode == "headers" && ($1 == "in:" || $1 == "name:")) next; 32 | if ($0 != "") { 33 | content = content "\n " $0; empty = 0 34 | } else if (!empty) { 35 | content = content "\n"; empty = 1 36 | } 37 | } 38 | 39 | ($0 ~ "^" name ":$") { 40 | content = "components:\n " mode ":\n " $0; empty = 0 41 | } 42 | 43 | END { 44 | if (content) { print content } 45 | }' "${SOURCES[@]}" > "${FILE}"; 46 | done; 47 | -------------------------------------------------------------------------------- /scripts/generate-rules-json.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # This script generates a JSON with information about the guideline rules. 3 | 4 | cat chapters/*.adoc | \ 5 | awk ' 6 | ($1 ~ /\[#[0-9]+\]/) { 7 | gsub("[\\]\\[]+", ""); 8 | keys[$1]=$1; 9 | _state=1; 10 | next; 11 | } 12 | (_state == 1) { 13 | gsub("[= ]+\\{", ""); 14 | gsub("\\}",""); 15 | gsub("\\\\","\\\\"); 16 | gsub("\"","\\\""); 17 | for (key in keys) { 18 | printf "{\"id\":\"" key "\", \"title\": \"" $0 "\"}\n"; 19 | delete keys[key]; 20 | } 21 | _state=2; 22 | next; 23 | }'; 24 | --------------------------------------------------------------------------------