{"id":14387,"date":"2016-08-16T12:15:40","date_gmt":"2016-08-16T09:15:40","guid":{"rendered":"https:\/\/www.webcodegeeks.com\/?p=14387"},"modified":"2016-08-12T14:17:13","modified_gmt":"2016-08-12T11:17:13","slug":"documenting-apis-preferences-matter","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/","title":{"rendered":"Documenting APIs when Preferences Matter"},"content":{"rendered":"<p>I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources to help you do just that. However, my head\u2019s been ironing out the wrinkles in the <a href=\"https:\/\/www.w3.org\/TR\/annotation-protocol\/\">Web Annotation Protocol<\/a> for the last few weeks, and I <em>really<\/em> wanted to see how the current blend of API documentation and description tooling fared against this rather minimal API.<\/p>\n<p>Here\u2019s what I\u2019ve discovered about the API documentation landscape when it comes to client preferences and describing content negotiation. It\u2019s not bleak\u2026 exactly\u2026 but it ain\u2019t rosy neither.<\/p>\n<h2>Preferential Treatment<\/h2>\n<p>Web Annotation Protocol traffics in three things:<\/p>\n<ul>\n<li>Annotations (duh)<\/li>\n<li>Annotation Pages (guess what\u2019s in here?)<\/li>\n<li>Annotation Collections\/Containers (essentially the front door)<\/li>\n<\/ul>\n<p>Annotations are the easy ones. They exist at a single URL. You request them, you get them back in the one and only flavor the specification lets you: Web Annotation Data Model.<\/p>\n<p>Annotation Collections on the other hand work to serve the needs of a wider audience: annotation clients. An annotation client might be a highlighting system, a bookmarking tool, a screenshot app, an image annotation system, a Natural Language Processing bot, or even the comments at the bottom of a web page. Given that wider audience, there are situations where the clients will be written with speed as their prime direction, but there are others who want the biggest, most verbose set of data they can get as one long streamed answer to their question.<\/p>\n<p>Here\u2019s <a href=\"https:\/\/www.w3.org\/TR\/annotation-protocol\/#client-preferences\">how the spec deals with all these preferences<\/a>. Clients can ask for:<\/p>\n<ol>\n<li>A minimal Annotation Container containing just a bit of \u201cintro\u201d and links to the Annotation Pages \u2014 this one\u2019s optimized clients that want small sets of content, but are happy to make lots of requests.<\/li>\n<li>A richer (and suggested default) Annotation Container which contains the first Annotation Page and it\u2019s \u201citems\u201d (the Annotations).<\/li>\n<\/ol>\n<p>Both of these variations are dictated by a single <code>Prefer<\/code> header (see <a href=\"http:\/\/tools.ietf.org\/html\/rfc7240\">RFC 7240<\/a> for more info). Here\u2019s the example request from the spec:<\/p>\n<pre class=\"brush:php\">GET \/annotations\/ HTTP\/1.1\r\nHost: example.org\r\nAccept: application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\"\r\nPrefer: return=representation;include=\"http:\/\/www.w3.org\/ns\/ldp#PreferMinimalContainer\"<\/pre>\n<p>This is the example (simple and harmless as it seems) that strained the gears of the current API documentation machinery. Let\u2019s have a look.<\/p>\n<h2>Round 1: OpenAPI (Swagger)<\/h2>\n<p><a href=\"https:\/\/openapis.org\/\">OpenAPI<\/a> (formerly known as Swagger) has risen to fame and has growing support from an equally growing list of companies thanks mostly to its move from a single-vendor (<a href=\"http:\/\/smartbear.com\">SmartBear<\/a> spec to a <a href=\"https:\/\/openapis.org\/\">collaborative project<\/a> hosted by the Linux Foundation.<\/p>\n<p>Having a collaborative environment (see <a href=\"https:\/\/openapis.org\/governance\">Governance<\/a>) gives any specification a richer, longer-lasting foundation than any single vendor product, project, and certainly specification. Foundations and consortiums give things about as much object permanence as one can hope for on the internets, so it\u2019s where I always start.<\/p>\n<p>Sadly, I hit a wall right out the gate.<\/p>\n<p>OpenAPI 2.0 (and its forerunners) are all rather JSON-centric. That didn\u2019t bother me as the Web Annotation Protocol also describes a singular JSON-based data encoding that it traffics in. OpenAPI, like the other options we\u2019ll see here, is also pretty endpoint specific. Also not a real problem. Web Annotation Protocol doesn\u2019t define URLs that you MUST use (that would be a Bad Thing, btw), but it does use some example URLs within the spec to make things more internally cohesive.<\/p>\n<p>My plan was to just mimic all the examples in the Web Annotation Protocol spec, but present them in OpenAPI 2.0, and call it a day. However, things fell over when I hit the section about Preferences:<\/p>\n<pre class=\"brush:php\">swagger: '2.0'\r\ninfo:\r\n  title: Web Annotation Protocol\r\nhost: example.org\r\nschemes:\r\n  - https\r\nbasePath: \/annotations\r\nproduces:\r\n  - application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\"\r\npaths:\r\n  \/annotations:\r\n    get:\r\n      summary: Annotation Collection \/ Container\r\n      description: |\r\n        Contains Annotations. Srsly.\r\n      parameters:\r\n        - name: Prefer\r\n          in: header\r\n          type: string\r\n          enum:\r\n           - 'return=representation;include=\"http:\/\/www.w3.org\/ns\/ldp#PreferMinimalContainer\"'\r\n      responses:\r\n        200:\r\n          description: |\r\n            An Annotation Collection with links to the\r\n            contained Annotation Pages and possibly the \r\n            completely contents of the first page\r\n            --depending on the preference of the client.\r\n          schema:\r\n            type: object\r\n            items:\r\n              $ref: '#\/definitions\/AnnotationCollection'<\/pre>\n<p>All seems well at first blush, but things fall a part at the word <code>responses<\/code>. The <a href=\"https:\/\/github.com\/OAI\/OpenAPI-Specification\/blob\/master\/versions\/2.0.md#responses-object\">Responses Object<\/a> contains keys based on <a href=\"http:\/\/httpstat.us\/\">HTTP status codes<\/a> and a <code>default<\/code> (catch-all) response. The value of each of these keys is a singular <a href=\"https:\/\/github.com\/OAI\/OpenAPI-Specification\/blob\/master\/versions\/2.0.md#responseObject\">Response Object<\/a>. Meaning, at this stage of the game, OpenAPI 2.0 doesn\u2019t support describing how an HTTP header would affect the outcome of a request to a specific URL.<\/p>\n<p>The above OpenAPI document presents the <code>Prefer<\/code> field and is even prepped for me to add the other options. However, there\u2019s now way to tell the reader (or the related tooling) about how that header may affect the results \u2014 unless it results in a unique HTTP status code, which isn\u2019t what I\u2019m needing.<\/p>\n<p>This also blocks describing other forms of HTTP Content Negotiation (see <a href=\"tools.ietf.org\/html\/rfc7231\">RFC 7231<\/a>). So on to Round 2!<\/p>\n<h2>Round 2: RAML<\/h2>\n<p><a href=\"http:\/\/raml.org\/\">RESTful API Modeling Language<\/a>, <em>a.k.a.<\/em> RAML, is specification built primarily by <a href=\"http:\/\/mulesoft.com\/\">MuleSoft<\/a>. It\u2019s been around for some time, and as it\u2019s grown, MuleSoft has done a great job of gathering more input from other companies and the community. They have a core <a href=\"http:\/\/raml.org\/about\/workgroup\">Workgroup<\/a> which oversees the process and progress of <a href=\"https:\/\/github.com\/raml-org\/raml-spec\">the specification<\/a>. As I was doing my digging, I was happy to see this gradual shift toward \u201cmore brains in the game.\u201d<\/p>\n<p>RAML is primarily written in YAML and at first blush feels very much like OpenAPI. So I was at once happy because of the familiarity but also concerned that I\u2019d hit the same glass ceiling. This time, I decided to dive straight into the spec. Here\u2019s what I found.<\/p>\n<p>Like OpenAPI, RAML:<\/p>\n<ul>\n<li>pivots around <a href=\"https:\/\/github.com\/raml-org\/raml-spec\/blob\/master\/versions\/raml-10\/raml-10.md#template-uris-and-uri-parameters\">Template URIs and URI Parameters<\/a>;<\/li>\n<li>followed by <a href=\"https:\/\/github.com\/raml-org\/raml-spec\/blob\/master\/versions\/raml-10\/raml-10.md#methods\">HTTP Methods<\/a>;<\/li>\n<li>and then requests requirements including optional <a href=\"https:\/\/github.com\/raml-org\/raml-spec\/blob\/master\/versions\/raml-10\/raml-10.md#headers\">Header definitions<\/a>, which get their own distinct space rather than a general <code>parameters<\/code> collection.<\/li>\n<li><a href=\"https:\/\/github.com\/raml-org\/raml-spec\/blob\/master\/versions\/raml-10\/raml-10.md#responses\">Responses<\/a> still group responses by status code.<\/li>\n<li><a href=\"https:\/\/github.com\/raml-org\/raml-spec\/blob\/master\/versions\/raml-10\/raml-10.md#resource-types-and-traits\">Resource Types and Traits<\/a> look like they <em>may<\/em> provide exactly what I need.<\/li>\n<\/ul>\n<p>But alas, resource types and traits turned out to mostly be \u201cmixins\u201d conceptually. Very handy to be sure, but unhelpful for fixing this negotiation problem.<\/p>\n<p>Here\u2019s what the RAML (using a <code>resourceType<\/code> for the <code>Prefer<\/code> header options) looks like:<\/p>\n<pre class=\"brush:php\">#%RAML 1.0\r\ntitle: Web Annotation Platform\r\nversion: 1.0.0\r\nmediaType: application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\"\r\n\r\nresourceTypes:\r\n  collection:\r\n    get:\r\n      headers:\r\n        Prefer:\r\n          type: string\r\n          enum:\r\n            - 'return=representation;include=\"http:\/\/www.w3.org\/ns\/ldp#PreferMinimalContainer\"'\r\n            - 'return=representation;include=\"http:\/\/www.w3.org\/ns\/oa#PreferContainedIRIs\"'\r\n            - 'return=representation;include=\"http:\/\/www.w3.org\/ns\/oa#PreferContainedDescriptions\"'\r\n\r\n\/annotations:\r\n  type: collection\r\n  get:\r\n    responses:\r\n      200:\r\n        body:\r\n          application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\":\r\n            example: |\r\n              {\r\n                \"@context\": [\r\n                  \"http:\/\/www.w3.org\/ns\/anno.jsonld\",\r\n                  \"http:\/\/www.w3.org\/ns\/ldp.jsonld\"\r\n                ],\r\n                \"id\": \"http:\/\/example.org\/annotations\/\",\r\n                \"type\": [\"BasicContainer\", \"AnnotationCollection\"],\r\n                \"total\": 42023,\r\n                \"label\": \"A Container for Web Annotations\",\r\n                \"first\": \"http:\/\/example.org\/annotations\/?page=0\",\r\n                \"last\": \"http:\/\/example.org\/annotations\/?page=42\"\r\n              }<\/pre>\n<p>One thing the does stand out here is the possibility for doing media type-based content negotiation. You can see just below the <code>body<\/code> key that there\u2019s this media type:<\/p>\n<p><code>application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\"<\/code><\/p>\n<p>It\u2019s also possible to stack those, so if <code>application\/json<\/code> could also be used for the same response, then you\u2019d present the above like so:<\/p>\n<pre class=\"brush:php\">body:\r\n    application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\":\r\n    application\/json:\r\n        example: |\r\n            {\"some\": \"json\"}<\/pre>\n<p>That\u2019s certainly a nice addition as it means you can describe content negotiation based around the <code>Accept<\/code> header. However, it does leave out the potential for the other core (as in RFC 2616 and its successor RFC 7231) header-based negotiation: <code>Accept-Charset<\/code>, <code>Accept-Encoding<\/code>, and <code>Accept-Language<\/code>. It also means we\u2019re at an impasse.<\/p>\n<p>There sadly doesn\u2019t seem to be a way to negotiate different representations based on an HTTP header in RAML either. So. On we go to Round 3!<\/p>\n<h2>Round 3: API Blueprint<\/h2>\n<p>Unlike the other two options we\u2019ve covered, <a href=\"https:\/\/apiblueprint.org\/\">API Blueprint<\/a> isn\u2019t based on YAML and\/or JSON. It\u2019s based on Markdown. It is, however, owned and managed by a single vendor: <a href=\"http:\/\/apiary.io\/\">Apiary.io<\/a>.<\/p>\n<p>The spec is openly licensed under the MIT, and Apiary does work hard to keep the process <a href=\"https:\/\/github.com\/apiaryio\/api-blueprint\/wiki\/Roadmap\">open and transparent<\/a>. They also have a <a href=\"https:\/\/github.com\/apiaryio\/api-blueprint-rfcs\">\u201cRequest for Comments\u201d repo<\/a> where heavier changes can be proposed via a clearly defined process. That said, Apiary.io is also an OpenAPI Initiative Member and supports OpenAPI documents in its tooling. Likely, there\u2019s lots of mind (and spec\/code) sharing in our collective futures \u2014 which is great!<\/p>\n<p>API Blueprint\u2019s Markdown-based approach has always intrigued me. It\u2019s the one format of the three that\u2019s written in a language you might write documentation in anyhow.<\/p>\n<p>Getting used to API Blueprint did take a bit, and understanding the various sections and their nesting options was also a little tricky. The APIairy.io editor was (of course) quite helpful in the validation process, and there are <a href=\"https:\/\/apiblueprint.org\/tools.html\">command line tools<\/a> for doing that locally.<\/p>\n<p>The great news though is that it does support what it calls <a href=\"https:\/\/apiblueprint.org\/documentation\/specification.html#example-multiple-transaction-examples\">\u201cMultiple Transactions\u201d for a single endpoint<\/a>! The key text from the <a href=\"https:\/\/apiblueprint.org\/documentation\/specification.html#action-section\">API Blueprint spec\u2019s Action section<\/a> reads:<\/p>\n<blockquote><p>An action section may consist of multiple HTTP transaction examples for the given HTTP request method.<\/p><\/blockquote>\n<p>Here\u2019s what the Web Annotation Protocol\u2019s <code>Prefer<\/code> header stuff we\u2019ve been hacking on this whole time looks like in API Blueprint:<\/p>\n<pre class=\"brush:php\">FORMAT: 1A\r\n\r\n# Web Annotation Protocol\r\n\r\nWeb Annotation Protocol is for retrieving and storing Web Annotation Data Model documents.\r\n\r\n## Annotation Collection [\/annotations\/]\r\n\r\n### GET\r\n\r\n+ Request PreferMinimalContainer\r\n\r\n    + Headers\r\n    \r\n            Prefer: return=representation;include=\"http:\/\/www.w3.org\/ns\/ldp#PreferMinimalContainer\"\r\n\r\n\r\n+ Response 200 (application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\")\r\n\r\n    + Schema\r\n\r\n            {\r\n              \"$schema\": \"http:\/\/json-schema.org\/draft-04\/schema#\",\r\n              \"type\": \"object\",\r\n              \"properties\": {\r\n                \"@context\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                },\r\n                \"id\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"type\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                },\r\n                \"total\": {\r\n                  \"type\": \"integer\"\r\n                },\r\n                \"label\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"first\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"last\": {\r\n                  \"type\": \"string\"\r\n                }\r\n              },\r\n              \"required\": [\r\n                \"@context\",\r\n                \"id\",\r\n                \"type\",\r\n                \"total\",\r\n                \"label\",\r\n                \"first\",\r\n                \"last\"\r\n              ]\r\n            }\r\n\r\n\r\n+ Request PreferContainedIRIs\r\n\r\n    + Headers\r\n    \r\n            Prefer: return=representation;include=\"http:\/\/www.w3.org\/ns\/oa#PreferContainedIRIs\"\r\n\r\n\r\n+ Response 200 (application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\")\r\n\r\n    + Schema\r\n    \r\n            {\r\n              \"$schema\": \"http:\/\/json-schema.org\/draft-04\/schema#\",\r\n              \"type\": \"object\",\r\n              \"properties\": {\r\n                \"@context\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                },\r\n                \"first\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                },\r\n                \"id\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"label\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"last\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"total\": {\r\n                  \"type\": \"integer\"\r\n                },\r\n                \"type\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                }\r\n              },\r\n              \"required\": [\r\n                \"@context\",\r\n                \"first\",\r\n                \"id\",\r\n                \"label\",\r\n                \"last\",\r\n                \"total\",\r\n                \"type\"\r\n              ]\r\n            }\r\n\r\n+ Request PreferContianedDescriptions\r\n\r\n    + Headers\r\n    \r\n            Prefer: return=representation;include=\"http:\/\/www.w3.org\/ns\/oa#PreferContainedDescriptions\"\r\n\r\n+ Response 200  (application\/ld+json; profile=\"http:\/\/www.w3.org\/ns\/anno.jsonld\")\r\n\r\n    + Schema\r\n    \r\n            {\r\n              \"$schema\": \"http:\/\/json-schema.org\/draft-04\/schema#\",\r\n              \"type\": \"object\",\r\n              \"properties\": {\r\n                \"@context\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                },\r\n                \"first\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"object\",\r\n                    \"properties\": {\r\n                      \"@context\": {\r\n                        \"type\": \"string\"\r\n                      },\r\n                      \"body\": {\r\n                        \"type\": [\"string\", \"object\", \"array\"]\r\n                      },\r\n                      \"id\": {\r\n                        \"type\": \"string\"\r\n                      },\r\n                      \"target\": {\r\n                        \"type\": [\"string\", \"object\", \"array\"]\r\n                      },\r\n                      \"type\": {\r\n                        \"type\": [\"array\", \"string\"]\r\n                      }\r\n                    },\r\n                    \"required\": [\r\n                      \"@context\",\r\n                      \"id\",\r\n                      \"target\",\r\n                      \"type\"\r\n                    ]\r\n                  }\r\n                },\r\n                \"id\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"label\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"last\": {\r\n                  \"type\": \"string\"\r\n                },\r\n                \"total\": {\r\n                  \"type\": \"integer\"\r\n                },\r\n                \"type\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                }\r\n              },\r\n              \"required\": [\r\n                \"@context\",\r\n                \"first\",\r\n                \"id\",\r\n                \"label\",\r\n                \"last\",\r\n                \"total\",\r\n                \"type\"\r\n              ]\r\n            }<\/pre>\n<p>See? It\u2019s just Markdown with some HTTP and JSON pasted in it. Pretty rad, actually.<\/p>\n<p>The Schema sections contain some quick <a href=\"http:\/\/jsonschema.net\/\">JSON Schemas that I generated<\/a> and then tweaked to make sure the basics were in place. This setup allowed me to list and name these multiple request options, each one noting a <code>Prefer<\/code> header key\/value combination that would result in the output seen in the \u201cResponse\u201d section that follows it.<\/p>\n<p>The end result is exactly what I\u2019d been attempting:<\/p>\n<ul>\n<li>document a single HTTP endpoint<\/li>\n<li>state unique headers for certain negotiated requests<\/li>\n<li>provide a validation system to confirm the response was as expected<\/li>\n<\/ul>\n<h3>Bonus: Doing Stuff With It<\/h3>\n<p>Wanting just a bit more than documentation out of the deal, I dove into the tools section and found the fabulous <a href=\"http:\/\/dredd.readthedocs.io\/en\/latest\/\">dredd<\/a> script. Dredd uses the API Blueprint documentation to test a running instance of the documented API!<\/p>\n<p>So, tempting fate, I opened my in-progress, Python-based Web Annotation Protocol implementation and ran: <code>dredd api-documentation.apib http:\/\/localhost:8080\/<\/code><\/p>\n<p>And it worked!<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.codeship.com\/wp-content\/uploads\/2016\/08\/dredd-screenshot.png\" width=\"917\" height=\"140\" \/><\/p>\n<p>Dredd properly sent the <code>Prefer<\/code> headers from those Request sections and checked the Responses against the Schema objects.<\/p>\n<blockquote><p>I\u2019ve elided some fiddly bits about me learning API Blueprint, getting <code>dredd<\/code> installed locally, dealing with Windows idiosyncrasies, and the like. It\u2019s boring, mind-numbing stuff. I\u2019m adding this note so that when you attempt to climb this mountain (or any other) and find rocks in your shoes: Don\u2019t be alarmed. Stop. Take them out. And climb some more. It\u2019s worth it in the end.<\/p><\/blockquote>\n<h2>Conclusion: Negotiable?<\/h2>\n<p>API Blueprint wins for API documentation formats that support content negotiation on more than the <code>Accept<\/code> header. Having the option to craft unique requests at a single endpoint is invaluable and puts API Blueprint at the top of my list for its flexibility and support for more of HTTP than its current contenders.<\/p>\n<p>Do you need content negotiation? That\u2019s negotiable.<\/p>\n<p><a href=\"https:\/\/tools.ietf.org\/html\/rfc7231#section-3.4\">Content Negotiation<\/a> is one of the untapped bits of awesome in HTTP. Often it gets overlooked because the tooling (as we\u2019ve seen here) doesn\u2019t lend itself to pointing a clear path for using it. However, even with wrinkles to iron out in some tools, the ability to have a single endpoint identify a conceptual resource from which you can negotiate various representations is invaluable.<\/p>\n<p>Content Negotiation is a key piece of the web\u2019s architecture that can benefit API developers and consumers. API Blueprint has the goods for documenting these APIs and even an ever-growing list of mock servers, testing clients, and proxies. Amazing.<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td><span class=\"reference\">Reference: <\/span><\/td>\n<td><a href=\"https:\/\/blog.codeship.com\/api-documentation-when-preferences-matter\/\">Documenting APIs when Preferences Matter<\/a> from our <a href=\"http:\/\/www.webcodegeeks.com\/join-us\/wcg\/\">WCG partner<\/a>\u00a0Benjamin Young at the <a href=\"http:\/\/blog.codeship.com\/\">Codeship Blog<\/a> blog.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources to help you do just that. However, my head\u2019s been ironing out the wrinkles in the Web Annotation Protocol for the last few weeks, and I really wanted &hellip;<\/p>\n","protected":false},"author":147,"featured_media":927,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[389],"class_list":["post-14387","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-apis"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Documenting APIs when Preferences Matter - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Documenting APIs when Preferences Matter - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2016-08-16T09:15:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Benjamin Young\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Benjamin Young\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/\"},\"author\":{\"name\":\"Benjamin Young\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/caa7920cfde365335817761979a72407\"},\"headline\":\"Documenting APIs when Preferences Matter\",\"datePublished\":\"2016-08-16T09:15:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/\"},\"wordCount\":1772,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg\",\"keywords\":[\"APIs\"],\"articleSection\":[\"Web Dev\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/\",\"name\":\"Documenting APIs when Preferences Matter - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg\",\"datePublished\":\"2016-08-16T09:15:40+00:00\",\"description\":\"I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Web Dev\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/web-development\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Documenting APIs when Preferences Matter\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/caa7920cfde365335817761979a72407\",\"name\":\"Benjamin Young\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/793ddd7d9809b369c04dbc92ea77a896112cc0a0e9a25417a43c17f4afb06f39?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/793ddd7d9809b369c04dbc92ea77a896112cc0a0e9a25417a43c17f4afb06f39?s=96&d=mm&r=g\",\"caption\":\"Benjamin Young\"},\"description\":\"Benjamin Young is a User Experience Engineer and Information Architect. He also organizes @RESTFest &amp; @OpenUpstate.\",\"url\":\"https:\/\/www.webcodegeeks.com\/author\/benjamin-young\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Documenting APIs when Preferences Matter - Web Code Geeks - 2026","description":"I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/","og_locale":"en_US","og_type":"article","og_title":"Documenting APIs when Preferences Matter - Web Code Geeks - 2026","og_description":"I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources","og_url":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2016-08-16T09:15:40+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg","type":"image\/jpeg"}],"author":"Benjamin Young","twitter_card":"summary_large_image","twitter_creator":"@webcodegeeks","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Benjamin Young","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/"},"author":{"name":"Benjamin Young","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/caa7920cfde365335817761979a72407"},"headline":"Documenting APIs when Preferences Matter","datePublished":"2016-08-16T09:15:40+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/"},"wordCount":1772,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg","keywords":["APIs"],"articleSection":["Web Dev"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/","url":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/","name":"Documenting APIs when Preferences Matter - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg","datePublished":"2016-08-16T09:15:40+00:00","description":"I sat down to write a \u201cDocument APIs with Open API and JSON Schema\u201d article. That\u2019s still quite possible, of course, and there\u2019s lots of great resources","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/10\/web-dev-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/web-development\/documenting-apis-preferences-matter\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Web Dev","item":"https:\/\/www.webcodegeeks.com\/category\/web-development\/"},{"@type":"ListItem","position":3,"name":"Documenting APIs when Preferences Matter"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/caa7920cfde365335817761979a72407","name":"Benjamin Young","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/793ddd7d9809b369c04dbc92ea77a896112cc0a0e9a25417a43c17f4afb06f39?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/793ddd7d9809b369c04dbc92ea77a896112cc0a0e9a25417a43c17f4afb06f39?s=96&d=mm&r=g","caption":"Benjamin Young"},"description":"Benjamin Young is a User Experience Engineer and Information Architect. He also organizes @RESTFest &amp; @OpenUpstate.","url":"https:\/\/www.webcodegeeks.com\/author\/benjamin-young\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/14387","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/147"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=14387"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/14387\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/927"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=14387"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=14387"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=14387"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}