{"id":2324,"date":"2022-12-13T15:51:43","date_gmt":"2022-12-13T23:51:43","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=2324"},"modified":"2023-03-13T08:53:15","modified_gmt":"2023-03-13T15:53:15","slug":"the-value-of-cadl-in-designing-apis","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/the-value-of-cadl-in-designing-apis\/","title":{"rendered":"The value of Cadl in designing APIs"},"content":{"rendered":"<h2>The value of Cadl in designing APIs<\/h2>\n<p><!-- cspell:ignore cadl openapi --><\/p>\n<p>As a developer, you want APIs that are well designed, intuitive, and easy to use. But you may encounter APIs that fall short on one or more of these dimensions, particularly in applications that combine data and services from multiple sources. The <a href=\"https:\/\/aka.ms\/azapi\/guidelines\">Azure REST API Guidelines<\/a> establish the design principles and API patterns to ensure that Azure APIs are well designed, intuitive, and easy to use. But ensuring that service teams adhere to these guidelines becomes challenging to apply as the number of Azure services continues to expand, teams become more distributed, and our API surface area grows.<\/p>\n<p>One challenge that we face is driving consistency in how our API definitions are written. Often, the API definitions are built by different individuals and teams within the same organization, and over time, can become large and complex. For example, the Azure Cognitive Services <a href=\"https:\/\/github.com\/Azure\/azure-rest-api-specs\/blob\/main\/specification\/cognitiveservices\/data-plane\/MetricsAdvisor\/stable\/v1.0\/MetricsAdvisor.json\">Metrics Advisor REST API definition<\/a> is almost 7,000 lines! In addition to consistency and size, we also struggle with reuse. While reuse is relatively easy in code, it&#8217;s challenging when working with OpenAPI documents.<\/p>\n<p>And Microsoft isn&#8217;t alone in these challenges. Many organizations are struggling through finding answers for how to build and manage APIs at scale. We&#8217;re seeing an emerging set of companies and tools that are working to provide solutions to difficult problems. One tool that Microsoft is beginning to have success with is <a href=\"https:\/\/microsoft.github.io\/cadl\/\">Cadl<\/a>, a new language for describing APIs.<\/p>\n<h2>The Cadl value proposition<\/h2>\n<p>Unlike other tools in the API space, Cadl is a language with a syntax that is similar to TypeScript, but built explicitly for describing APIs. In fact, we sometimes describe Cadl as &#8220;TypeScript for OpenAPI&#8221;. Because Cadl is a language, it provides many benefits that other tools don&#8217;t.<\/p>\n<ul>\n<li>Familiar syntax: Cadl borrows many language elements from popular programming languages like TypeScript and C#, so it&#8217;s easy for developers to learn and use.<\/li>\n<li>Great tooling support: IDE extensions in VS Code and Visual Studio include syntax highlighting, go to definition, linting, and auto-completion.<\/li>\n<li>Extensible: Developers can codify their API guidelines and best practices into a set of rules that can be enforced by Cadl.<\/li>\n<li>Highly expressive: API descriptions are concise and core concepts encapsulated into common libraries.<\/li>\n<\/ul>\n<p>Authoring your API, you can compile your Cadl file to emit a complete OpenAPI definition that can be used to drive your existing tool chain. Cadl ensures that your API definition is correct and accurate. Just like you can&#8217;t compile a C# class with errors, you can&#8217;t produce an OpenAPI definition that doesn&#8217;t adhere to your rules and guidelines. By using Cadl, all of your APIs will be &#8220;correct by construction.&#8221;<\/p>\n<h2>Create an API design with Cadl<\/h2>\n<p>The code for this example is located at <a href=\"https:\/\/github.com\/APIPatterns\/cadl-demo\">cadl-demo<\/a>, and originated from the Http example in the <a href=\"https:\/\/aka.ms\/trycadl\">Cadl-playground<\/a>. It will help accelerate your understanding of this post if you load up this example and get familiar with Cadl. Cadl has rich tooling support in VS Code and Visual Studio, which gives developers full language support and makes refactoring APIs super easy. We&#8217;ll use VS Code and the OpenAPI extension for the rest of this post. We&#8217;ll also use examples from the <a href=\"https:\/\/aka.ms\/azapi\/guidelines\">Azure REST API Guidelines<\/a> to illustrate how you can codify your own API standards.<\/p>\n<h3>Get started<\/h3>\n<p>Open the <a href=\"https:\/\/aka.ms\/cadl\">Cadl docs<\/a> and select the <a href=\"https:\/\/microsoft.github.io\/cadl\/introduction\/installation\">Installation tab<\/a> and then follow the steps there to install the tools and create a new Cadl project. The new empty Cadl project should contain:<\/p>\n<pre><code class=\"language-text\">package.json      # Package manifest defining your cadl project as a node package.\r\ncadl-project.yaml # Cadl project configuration letting you configure emitters, emitter options, compiler options, etc.\r\nmain.cadl         # Cadl entrypoint<\/code><\/pre>\n<p>Next, we&#8217;ll create our initial Cadl file using the <a href=\"https:\/\/aka.ms\/trycadl\">Cadl playground<\/a>. The Cadl playground is a great place to experiment with and share simple Cadl definitions. Open the playground and select the <strong>Http service<\/strong> example. View the Swagger UI by choosing it from the dropdown in the right pane. We&#8217;ll make a few small changes that will help illustrate some points later in the discussion:<\/p>\n<ul>\n<li>Add <code>version: 1.0.0<\/code> to the service decorator<\/li>\n<li>Change <code>@key<\/code> to <code>@path<\/code> on the <code>id<\/code> property in the Widgets model<\/li>\n<li>Add <code>@route(\"\/widgets\")<\/code> and <code>@tag(\"Widgets\")<\/code> to the Widgets interface<\/li>\n<li>Remove <code>@route<\/code> from read<\/li>\n<li>Replace the <code>@get customRead<\/code> operation with <code>@route(\"analyze\") @post analyze()<\/code> that returns <code>string | Error<\/code>;<\/li>\n<\/ul>\n<p>The result should look like this:<\/p>\n<pre><code class=\"language-typescript\">import \"@cadl-lang\/rest\";\r\n\r\n@service({\r\n  title: \"Widget Service\",\r\n  version: \"1.0.0\"\r\n})\r\nnamespace DemoService;\r\nusing Cadl.Http;\r\n\r\nmodel Widget {\r\n  @path id: string;\r\n  weight: int32;\r\n  color: \"red\" | \"blue\";\r\n}\r\n\r\n@error\r\nmodel Error {\r\n  code: int32;\r\n  message: string;\r\n}\r\n\r\n@route(\"\/widgets\")\r\n@tag(\"Widgets\")\r\ninterface Widgets {\r\n  @get list(): Widget[] | Error;\r\n  @get read(@path id: string): Widget | Error;\r\n  @post create(...Widget): Widget | Error;\r\n  @route(\"analyze\") @post analyze(): string | Error;\r\n}<\/code><\/pre>\n<p>Copy the Cadl from the playground to the <code>main.cadl<\/code> file in your VS Code project. Now you can run <code>cadl compile .<\/code> in your VS Code terminal and it will produce an <code>openapi.json<\/code> file in the <code>cadl-output<\/code> directory with your generated OpenAPI 3 definition. If you open the <code>openapi.json<\/code> in VS Code and then open the Swagger UI view, it should look exactly like the view in the Cadl playground.<\/p>\n<h3>Add gadgets<\/h3>\n<p>Most services will have more than one resource (model) and endpoint. In this example, we&#8217;ll add the <code>Gadget<\/code> resource to our API definition. Using Cadl, it&#8217;s easy to add another model:<\/p>\n<pre><code class=\"language-typescript\">model Gadget {\r\n  @path id: string;\r\n  height: float32;\r\n  width: float32;\r\n  color: \"green\" | \"yellow\";\r\n}<\/code><\/pre>\n<p>Now we want to add an endpoint for our new <code>Gadget<\/code> resource. We could copy and paste the <code>Widgets<\/code> interface, but then, we&#8217;d have to copy\/paste the same operation definition for all endpoints! As developers, we prefer a way to define something once, and reuse it wherever we need it. Cadl supports this type of reuse for most API elements.<\/p>\n<h2>Build a Cadl library<\/h2>\n<p>Let&#8217;s use the power of the Cadl language to define an <em>interface template<\/em> that can be reused across all of our resources. We&#8217;ll create a new <code>library.cadl<\/code> file to contain this template and any common definitions. When a developer defines and new resource, they can define all the standard operations for the resource by <em>extending<\/em> the resource template interface. In this way, when the Cadl file is compiled, and the OpenAPI document emitted, it will have the required operations, constructed in exactly the way we defined them.<\/p>\n<p>We&#8217;ll move the <code>Error<\/code> model from the <code>main.cadl<\/code> to the new file and then define an interface template that defines a <code>list<\/code>, <code>create<\/code>, and <code>get<\/code> operation similar to our <code>Widgets<\/code> interface, using a type parameter in place of the <code>Widget<\/code> model. The <code>library.cadl<\/code> now looks like:<\/p>\n<pre><code class=\"language-typescript\">import \"@cadl-lang\/rest\";\r\n\r\nusing Cadl.Http;\r\n\r\n@error\r\nmodel Error {\r\n  code: int32;\r\n  message: string;\r\n}\r\n\r\ninterface ResourceInterface&lt;T&gt; {\r\n    @get list(): T[] | Error;\r\n    @get read(@path id: string): T | Error;\r\n    @post create(...T): T | Error;\r\n  }<\/code><\/pre>\n<p>Back in the <code>main.cadl<\/code> file, we can now use the new <code>ResourceInterface<\/code> template for both <code>Widgets<\/code> and <code>Gadgets<\/code> as follows:<\/p>\n<pre><code class=\"language-typescript\">@route(\"\/widgets\")\r\n@tag(\"Widgets\")\r\ninterface Widgets extends ResourceInterface&lt;Widget&gt; {\r\n  @route(\"analyze\") @post analyze(): string | Error;\r\n}\r\n\r\n@route(\"\/gadgets\")\r\n@tag(\"Gadgets\")\r\ninterface Gadgets extends ResourceInterface&lt;Gadget&gt; {\r\n}<\/code><\/pre>\n<p>Notice that we kept the <code>analyze<\/code> operation of <code>Widgets<\/code> because it isn&#8217;t part of the &#8220;standard&#8221; interface for all resources.<\/p>\n<p>At this point, it&#8217;s worth noting that our Cadl definition is a mere 51 lines across two files but produces an OpenAPI definition that is 470 lines\u2014an order of magnitude difference.<\/p>\n<h2>Integrate API guidelines<\/h2>\n<p>Next let&#8217;s look at how Cadl allows you to build API guidelines into your Cadl library. Organizations often create API guidelines to establish patterns for APIs that are well-designed, intuitive, and easy to use. Here, we&#8217;ll use <a href=\"https:\/\/aka.ms\/azapi\/guidelines\">Azure&#8217;s REST API guidelines<\/a> as an example.<\/p>\n<h3>Error response<\/h3>\n<p><a href=\"https:\/\/github.com\/microsoft\/api-guidelines\/blob\/vNext\/azure\/Guidelines.md#handling-errors\">Azure&#8217;s API guidelines<\/a> require an operation error response to have a standard structure and contents, in particular:<\/p>\n<p>\u2705\u00a0DO\u00a0return an\u00a0<code>x-ms-error-code<\/code>\u00a0response header with a string error code indicating what went wrong.<\/p>\n<p>\u2705\u00a0DO\u00a0provide a response body with the following structure<\/p>\n<p>The error response in our current Cadl definition doesn&#8217;t meet these requirements, but that is easily remedied with a few changes to the error model in <code>library.cadl<\/code>. Since all operations are already using this one error model, we only need to make the change in this one place. Here&#8217;s the new definition that complies with the above guidelines:<\/p>\n<pre><code class=\"language-typescript\">@error\r\n@doc(\"Error response\")\r\nmodel Error {\r\n    @doc(\"A server-defined code that uniquely identifies the error.\")\r\n    @header(\"x-ms-error-code\")\r\n    code: string;\r\n    @doc(\"Top-level error object\")\r\n    error: ErrorDetail;\r\n}\r\n\r\n@doc(\"Error details\")\r\nmodel ErrorDetail {\r\n  @doc(\"A server-defined code that uniquely identifies the error.\")\r\n  code: string;\r\n  @doc(\"A human-readable representation of the error.\")\r\n  message: string;\r\n  @doc(\"An array of details about specific errors that led to this reported error.\")\r\n  details?: ErrorDetail[];\r\n}<\/code><\/pre>\n<p>We&#8217;ve also added <code>@doc<\/code> decorators on the models and properties to produce descriptions on these elements in the generated OpenAPI, which will be shown in the REST API documentation generated from the OpenAPI.<\/p>\n<h3>Standard operations<\/h3>\n<p>Next, we&#8217;ll tackle the standard operations in our <code>ResourceInterface<\/code> template. The <a href=\"https:\/\/github.com\/microsoft\/api-guidelines\/blob\/vNext\/azure\/Guidelines.md#collections\">Azure API Guidelines<\/a> require the response of a list operation to have a specific structure that supports extension and pagination (where needed). Specifically:<\/p>\n<p>\u2705\u00a0DO\u00a0structure the response to a list operation as an object with a top-level array field containing the set (or subset) of resources.<\/p>\n<p>\u2705\u00a0DO\u00a0return a\u00a0<code>nextLink<\/code>\u00a0field with an absolute URL that the client can GET in order to retrieve the next page of the collection.<\/p>\n<p>\u2611\ufe0f\u00a0YOU SHOULD\u00a0use\u00a0value\u00a0as the name of the top-level array field unless a more appropriate name is available.<\/p>\n<p>Azure also requires all operations to be idempotent, including creates. Most resources also need to support an update operation and a delete operation. Azure&#8217;s specific guidelines are:<\/p>\n<p>\u2705\u00a0DO\u00a0ensure that\u00a0all\u00a0HTTP methods are idempotent.<\/p>\n<p>\u2611\ufe0f\u00a0YOU SHOULD\u00a0use PUT or PATCH to create a resource. These HTTP methods are easy to implement, allow the customer to name their own resource, and are idempotent.<\/p>\n<p>\u2705\u00a0DO\u00a0create and update resources using PATCH [RFC5789] with JSON Merge Patch\u00a0(RFC7396)\u00a0request body.<\/p>\n<p>\u2705\u00a0DO\u00a0return a\u00a0204-No Content\u00a0without a resource\/body for a DELETE operation.<\/p>\n<p>Once again, we can comply with all the above guidelines with some straightforward changes to the <code>library.cadl<\/code> file:<\/p>\n<ul>\n<li>Define a model for the response structure of a list operation.<\/li>\n<li>Define a model for the JSON Merge Patch request body.<\/li>\n<li>Update the <code>list<\/code> operation to use the new response model.<\/li>\n<li>Change the <code>create<\/code> operation to use the put method.<\/li>\n<li>Add an update operation that accepts the new <code>Patch<\/code> model.<\/li>\n<li>Add a delete operation that returns <code>void<\/code>, which becomes a 204 response.<\/li>\n<\/ul>\n<p>Here&#8217;s what the <code>ResourceInterface<\/code> looks like in our <code>library.cadl<\/code> file. Notice that it has all the standard operations you would expect. <code>GET<\/code> operations for the collection, a <code>GET<\/code> with an <code>id<\/code> for a specific resource, and the create, update, and delete operations with <code>PUT<\/code>, <code>PATCH<\/code>, and <code>DELETE<\/code> respectively. Here&#8217;s the changed section of <code>library.cadl<\/code>:<\/p>\n<pre><code class=\"language-typescript\">model List&lt;T&gt; {\r\n    @doc(\"List of elements\")\r\n    value: T[];\r\n    @doc(\"A link to the next page of results if present.\")\r\n    nextLink?: url;\r\n}\r\n\r\nmodel Patch&lt;T&gt; {\r\n    @header contentType: \"application\/merge-patch+json\";\r\n    ...T;\r\n}\r\n\r\ninterface ResourceInterface&lt;T&gt; {\r\n    @get list(): List&lt;T&gt; | Error;\r\n    @get read(@path id: string): T | Error;\r\n    @put create(...T): T | Error;\r\n    @patch update(...Patch&lt;T&gt;): T | Error;\r\n    @delete delete(@path id: string): void | Error;\r\n}<\/code><\/pre>\n<h3>API versioning<\/h3>\n<p>Change is inevitable in all things including APIs, and Azure requires all APIs to implement explicit versioning support in the form of a required <code>api-version<\/code> query parameter on every operation. Here are some of the specific requirements from the <a href=\"https:\/\/github.com\/microsoft\/api-guidelines\/blob\/vNext\/azure\/Guidelines.md#api-versioning\">Azure API Guidelines<\/a>.<\/p>\n<p>\u2705\u00a0DO\u00a0use a required query parameter named\u00a0<code>api-version<\/code>\u00a0on every operation for the client to specify the API version.<\/p>\n<p>\u2705\u00a0DO\u00a0use\u00a0&#8220;YYYY-MM-DD&#8221;\u00a0date values, with a\u00a0<code>-preview<\/code>\u00a0suffix for preview versions, as the valid values for\u00a0<code>api-version<\/code>.<\/p>\n<p>Since the <code>api-version<\/code> query parameter must be passed on all operations, it makes sense to define it once in the <code>library.cadl<\/code> file. Here&#8217;s what that might look like:<\/p>\n<pre><code class=\"language-typescript\">model ApiVersion {\r\n    @doc(\"The version of the API in the form YYYY-MM-DD\")\r\n    @query(\"api-version\") apiVersion: string;\r\n}<\/code><\/pre>\n<p>Then we add the <code>apiVersion<\/code> parameter to each operation in the <code>ResourceInterface<\/code> template using the <em>spread<\/em> operator on the <code>ApiVersion<\/code> model. Changing the template ensures that all the <code>Widgets<\/code> and <code>Gadgets<\/code> operations that derive from the template come into compliance with the above guidelines.<\/p>\n<pre><code class=\"language-typescript\">interface ResourceInterface&lt;T&gt; {\r\n    @get list(...ApiVersion): List&lt;T&gt; | Error;\r\n    @get read(@path id: string, ...ApiVersion): T | Error;\r\n    @put create(...T, ...ApiVersion): T | Error;\r\n    @patch update(...Patch&lt;T&gt;, ...ApiVersion): T | Error;\r\n    @delete delete(@path id: string, ...ApiVersion): void | Error;\r\n}<\/code><\/pre>\n<p>But we&#8217;re not yet fully in compliance with the API versioning requirements, because there&#8217;s still the <code>analyze<\/code> operation of the <code>Widgets<\/code> interface over in <code>main.cadl<\/code> that doesn&#8217;t have an <code>api-version<\/code> parameter. And as the API continues to grow, there may be other operations outside <code>library.cadl<\/code> that we need to make sure include an <code>api-version<\/code> parameter. Cadl has a solution for this problem: built-in linter support.<\/p>\n<h3>Lint with Cadl<\/h3>\n<p>Cadl allows libraries to define a function that checks a Cadl program for specific patterns and issue diagnostics (linter messages). Typically, these linter functions are written in TypeScript and then compiled and run as JavaScript as part of the Cadl compilation. For expediency, we&#8217;ll implement our linter in JavaScript directly, but we don&#8217;t endorse this approach for a production Cadl library.<\/p>\n<p>We&#8217;ll implement our linter in a file called <code>linter.js<\/code>, which we import into <code>library.cadl<\/code> to activate it. Our linter will use functions from the <code>@cadl-lang\/lint<\/code> library so we need to install that library in our project. You can see the full source code for <code>linter.js<\/code> in the <a href=\"https:\/\/github.com\/APIPatterns\/cadl-demo\/blob\/608b9d3369cbc01894423367e49e04b613abcb66\/linter.js\">cadl-demo project<\/a>. Here are a few key points to focus on:<\/p>\n<ul>\n<li>Lines 9-15 define the <code>version-policy<\/code> diagnostic, which will be issued when the linter finds an operation without an <code>api-version<\/code> query parameter.<\/li>\n<li>Lines 20-22 define a function that checks if a parameter has the necessary attributes of an <code>api-version<\/code> parameter&#8211;its name, it&#8217;s required (not optional), and it&#8217;s a string type.<\/li>\n<li>Lines 25-40 define the <code>versionPolicyRule<\/code>, which checks each operation (line 26) to see if &#8220;some&#8221; parameter is an <code>apiVersion<\/code> parameter and a query parameter. If not, the diagnostic is issued.<\/li>\n<li>Lines 46-47 register and enable the rule.<\/li>\n<\/ul>\n<p>With the linter now included in our library, <code>cadl compile .<\/code> will issue a diagnostic and fail the compile if any operation is missing the <code>api-version<\/code> query parameter:<\/p>\n<pre><code class=\"language-bash\">&gt;cadl compile .\r\nCadl compiler v0.38.3\r\n\r\n\/Users\/mikekistler\/Projects\/mikekistler\/cadl-demo\/main.cadl:27:3 - error myLibrary\/version-policy: Every operation must have a required 'api-version' query parameter\r\n&gt; 27 |   @route(\"analyze\") @post analyze(): string | Error;\r\n     |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n\r\nFound 1 error.<\/code><\/pre>\n<h2>Summary<\/h2>\n<p>In this post, we&#8217;ve taken you through some of the key value propositions of Cadl:<\/p>\n<ul>\n<li>Cadl provides a natural and concise expression of API constructs that produces a standard OpenAPI v3 API definition.<\/li>\n<li>Cadl enables definition and reuse of API patterns.<\/li>\n<li>Cadl makes it easy to incorporate your organization&#8217;s API guidelines into these reusable patterns so that when developers use them they automatically get compliant APIs.<\/li>\n<li>Cadl allows you to build linters right into your reusable libraries that signal to developers when they&#8217;ve possibly crossed one of your guidelines.<\/li>\n<\/ul>\n<p>All these features add up to a system that enables developers to build APIs that are well designed, intuitive, and easy to use.<\/p>\n<p>There&#8217;s much more to share on Cadl, such as how to create standalone libraries that can be reused across many projects, advanced API patterns, creating customer emitters, and more. Look for more posts on Cadl coming soon!<\/p>\n<p><a href=\"https:\/\/microsoft.github.io\/cadl\">Cadl<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This blog post describes the new Cadl API design language and its benefits for API authoring.<\/p>\n","protected":false},"author":68772,"featured_media":2323,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[830,876,831,777,829,896],"class_list":["post-2324","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-api","tag-cadl","tag-guidelines","tag-openapi","tag-rest","tag-typespec"],"acf":[],"blog_post_summary":"<p>This blog post describes the new Cadl API design language and its benefits for API authoring.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/2324","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/68772"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=2324"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/2324\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/2323"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=2324"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=2324"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=2324"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}