<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Octri</title>
    <description>The latest articles on DEV Community by Octri (@octri).</description>
    <link>https://dev.to/octri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4173521%2F67037417-f1ee-4077-9950-392dbaca871c.jpg</url>
      <title>DEV Community: Octri</title>
      <link>https://dev.to/octri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/octri"/>
    <language>en</language>
    <item>
      <title>Why generated SDKs break silently, and how to make them observable</title>
      <dc:creator>Octri</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:23:19 +0000</pubDate>
      <link>https://dev.to/octri/why-generated-sdks-break-silently-and-how-to-make-them-observable-p61</link>
      <guid>https://dev.to/octri/why-generated-sdks-break-silently-and-how-to-make-them-observable-p61</guid>
      <description>&lt;p&gt;You generate an SDK from your OpenAPI spec, publish it to npm and PyPI, and integrators start building on it. Then it goes dark.&lt;/p&gt;

&lt;p&gt;Once that client library is running inside someone else's production application, you have almost no visibility into how it behaves. An endpoint starts returning a shape the SDK doesn't expect. A serialization path throws for a subset of inputs. A retry policy turns a blip into a storm against your API. And the first you hear about any of it is a GitHub issue, a support ticket, or a customer who quietly churns without ever filing one.&lt;/p&gt;

&lt;p&gt;We instrument our servers exhaustively. We wrap our own front-ends in Sentry. But the SDKs we ship to &lt;em&gt;other&lt;/em&gt; developers, arguably the most important surface of an API product, are the one place we fly completely blind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "just add Sentry" never happens
&lt;/h2&gt;

&lt;p&gt;In principle, every integrator could wrap your SDK in their own error monitoring. In practice they don't, and even when they do it doesn't help you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's their Sentry, not yours.&lt;/strong&gt; The errors that reveal a bug in &lt;em&gt;your&lt;/em&gt; SDK land in dozens of separate customer dashboards you'll never see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The stack traces are useless.&lt;/strong&gt; They point into minified, bundled SDK code your integrator doesn't own and can't read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There's no shared release identity.&lt;/strong&gt; Nothing connects the code you generated to the build the error came from.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the signal that would tell you your SDK has a problem is scattered, minified into noise, and invisible to the one team that could fix it: you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "observable generated SDKs" actually requires
&lt;/h2&gt;

&lt;p&gt;Three pieces have to line up. Miss any one and you're back to guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Runtime error reporting from inside the SDK
&lt;/h3&gt;

&lt;p&gt;The generated client captures failures and reports them with context: endpoint, status, SDK version, and, critically, a &lt;strong&gt;release tag&lt;/strong&gt;. Only the SDK author can build this in. It has to live in the generated code itself. In Octri this is a toggle plus an SDK rebuild, since the telemetry config is compiled in at build time: see &lt;a href="https://docs.octri.dev/monitoring/guides/setup" rel="noopener noreferrer"&gt;Setup&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Source maps, uploaded from the right build
&lt;/h3&gt;

&lt;p&gt;A minified stack trace is noise until you pair it with the source map from the exact build that produced it. Here's the part that trips people up. Those maps get produced when the &lt;strong&gt;application&lt;/strong&gt; bundles, in your integrator's CI, on every deploy. &lt;em&gt;Not&lt;/em&gt; when the SDK is generated. So they have to be uploaded separately, tagged with the same release. It's the model &lt;code&gt;sentry-cli sourcemaps upload&lt;/code&gt; already uses. Minified languages upload &lt;a href="https://docs.octri.dev/cli/guides/sourcemaps" rel="noopener noreferrer"&gt;source maps&lt;/a&gt;; compiled ones like Go, Rust, and Java upload &lt;a href="https://docs.octri.dev/cli/guides/sources" rel="noopener noreferrer"&gt;sources&lt;/a&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Release pairing on read
&lt;/h3&gt;

&lt;p&gt;When an error arrives tagged &lt;code&gt;release: abc123&lt;/code&gt;, the service looks up the source map tagged &lt;code&gt;abc123&lt;/code&gt; and de-minifies the trace against it. The match is the whole trick. Get the tags out of sync and every trace resolves to nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule that makes it work
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;release&lt;/code&gt; your SDK reports at runtime &lt;strong&gt;must equal&lt;/strong&gt; the &lt;code&gt;release&lt;/code&gt; you tagged when you uploaded source maps. In practice, that's the git SHA of the same build. That one invariant is what lets an incoming production error pair with the right map and de-minify to your original source. Everything else is plumbing. This is the load-bearing constraint.&lt;/p&gt;

&lt;p&gt;If you're implementing this on Octri rather than reading it as theory, &lt;a href="https://docs.octri.dev/monitoring/guides/symbolication" rel="noopener noreferrer"&gt;readable stack traces&lt;/a&gt; is the guide for exactly this rule, including how to check the release with &lt;code&gt;--dry-run&lt;/code&gt; before you deploy and why you should never serve those maps to users. &lt;a href="https://docs.octri.dev/cli/guides/ci" rel="noopener noreferrer"&gt;CI setup&lt;/a&gt; puts the upload on every build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get once it's wired up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A production error in a customer's app arrives as a &lt;strong&gt;clean, de-minified stack trace&lt;/strong&gt; that points straight at &lt;em&gt;your&lt;/em&gt; source. Errors group into &lt;a href="https://docs.octri.dev/monitoring/guides/issues" rel="noopener noreferrer"&gt;issues&lt;/a&gt;, so one bad deploy is one problem rather than a thousand.&lt;/li&gt;
&lt;li&gt;You can see &lt;strong&gt;which endpoints break for real integrators&lt;/strong&gt;, ranked by frequency. It's a prioritized bug list you never had to ask anyone for.&lt;/li&gt;
&lt;li&gt;You fix integration bugs &lt;strong&gt;before&lt;/strong&gt; they turn into tickets, bad reviews, or churn. Ship the fix and watch that build in &lt;a href="https://docs.octri.dev/monitoring/guides/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt;, or &lt;a href="https://docs.octri.dev/monitoring/guides/alerts" rel="noopener noreferrer"&gt;alert&lt;/a&gt; on the error budget so the next one finds you first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;SDK generation shouldn't end at "here's your client library." Shipping the library is where its real life begins. The most interesting failures happen out there in production, in code you generated but don't run. Treat generated SDKs like any other production surface. Make them observable.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the bet we made with **Octri&lt;/em&gt;&lt;em&gt;. Every SDK it generates from your OpenAPI spec carries opt-in runtime reporting, plus a source-map upload command in the &lt;code&gt;octri&lt;/code&gt; CLI and release pairing. SDK users turn reporting on. Docs and SDKs in 10 languages, monitored, from one spec. &lt;a href="https://octri.dev" rel="noopener noreferrer"&gt;Try it free on your own spec.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Read next: &lt;a href="https://docs.octri.dev/monitoring/guides/overview" rel="noopener noreferrer"&gt;the monitoring docs&lt;/a&gt; for how it works in the dashboard, or &lt;a href="https://octri.dev/blog/octri-monitoring" rel="noopener noreferrer"&gt;Inside Octri Monitoring&lt;/a&gt; for a tour of the views you get.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>api</category>
      <category>monitoring</category>
      <category>observability</category>
    </item>
    <item>
      <title>Publishing SDKs to npm, PyPI, Maven Central and the rest</title>
      <dc:creator>Octri</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:18:01 +0000</pubDate>
      <link>https://dev.to/octri/publishing-sdks-to-npm-pypi-maven-central-and-the-rest-418m</link>
      <guid>https://dev.to/octri/publishing-sdks-to-npm-pypi-maven-central-and-the-rest-418m</guid>
      <description>&lt;p&gt;Generating a client library is the easy half. Getting it into the place your users already look, under a name you can live with, is where the irreversible decisions are.&lt;/p&gt;

&lt;p&gt;Most of them are naming decisions, and every registry punishes a bad one differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The registries, and what each wants
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;npm, for TypeScript.&lt;/strong&gt; Scoped (&lt;code&gt;@acme/api&lt;/code&gt;) or unscoped (&lt;code&gt;acme-api&lt;/code&gt;). Take the scope. It is namespaced to your organisation, it cannot be squatted, and it reads unambiguously beside your other packages. npm has no rename, and it refuses to republish a version you have deleted, so the first publish claims the name permanently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PyPI, for Python.&lt;/strong&gt; The distribution name is what users type after &lt;code&gt;pip install&lt;/code&gt;. It is normalised, so &lt;code&gt;acme_api&lt;/code&gt; and &lt;code&gt;acme-api&lt;/code&gt; are the same name to the index and you cannot have both. The import name is a separate thing and does not have to match, which surprises people: &lt;code&gt;pip install acme-api&lt;/code&gt; giving you &lt;code&gt;import acme_api&lt;/code&gt; is normal and fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maven Central, for Java and Kotlin.&lt;/strong&gt; Coordinates are &lt;code&gt;groupId:artifactId&lt;/code&gt;, and the groupId must be a domain you control. Sonatype verifies it before your first release, not after. &lt;code&gt;com.acme&lt;/code&gt; means proving you own &lt;code&gt;acme.com&lt;/code&gt;. This is the single longest lead time in any SDK launch and it is the one nobody schedules for, because every other registry lets you publish in five minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;crates.io, for Rust.&lt;/strong&gt; Flat namespace, no scopes, first come first served. Names are effectively permanent. Reserve yours early even if you are not ready to publish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RubyGems, for Ruby.&lt;/strong&gt; Flat namespace too. Convention is dashes in the gem name and slashes in the require path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Packagist, for PHP.&lt;/strong&gt; &lt;code&gt;vendor/package&lt;/code&gt;, wired to a git repository rather than an upload. Composer resolves from tags, so your release process is a tagging process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pub.dev, for Dart.&lt;/strong&gt; Flat namespace, and it scores packages publicly on documentation and maintenance. That score is on the page your users read, so the README is not optional in the way it is elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go and Swift have no registry
&lt;/h2&gt;

&lt;p&gt;This catches people out because it inverts everything above.&lt;/p&gt;

&lt;p&gt;Go modules and Swift Package Manager resolve straight from a git repository. There is no account to connect, no upload, no publish step. You push a tagged commit and &lt;code&gt;go get&lt;/code&gt; finds it.&lt;/p&gt;

&lt;p&gt;Which sounds simpler and is more dangerous, because of one thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Go, the repository URL is the import path.&lt;/strong&gt; Go derives the module path from the repo and writes it into &lt;code&gt;go.mod&lt;/code&gt;, so &lt;code&gt;github.com/acme/acme-go&lt;/code&gt; is the line every consumer types at the top of their file. Rename or move that repository and every import breaks, for everyone, with no deprecation path and no redirect. There is no equivalent of an npm deprecation notice.&lt;/p&gt;

&lt;p&gt;Pick the Go repository name deliberately, before the first tag, and expect it to differ from the repository your other languages use. &lt;code&gt;acme-go&lt;/code&gt; is a repository name. &lt;code&gt;acme-node&lt;/code&gt; is a different one. They should not be the same repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  One repo per language, or one repo for all
&lt;/h2&gt;

&lt;p&gt;Both work. The tradeoffs are not symmetric.&lt;/p&gt;

&lt;p&gt;Separate repositories per language are the default for a reason: Go forces it (the import path is the repo), release tags do not collide, each language gets its own issue tracker, and a consumer cloning your Python SDK does not download six other languages.&lt;/p&gt;

&lt;p&gt;A monorepo is tidier for you and worse for them. If you go that way, Go still needs its own, so you end up with a monorepo plus an exception, which is usually the worst of both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to settle before the first publish
&lt;/h2&gt;

&lt;p&gt;A short list, all of it permanent or expensive to change:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The package name in every registry.&lt;/strong&gt; Check availability everywhere at once, before you pick. Discovering that your name is taken on crates.io after you have shipped on npm means either an inconsistent set of names or a rename.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Maven groupId, and start the verification.&lt;/strong&gt; It gates your Java launch and it is out of your hands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Go repository.&lt;/strong&gt; See above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The version you start at.&lt;/strong&gt; &lt;code&gt;1.0.0&lt;/code&gt; is a claim about stability. &lt;code&gt;0.1.0&lt;/code&gt; buys room to change the shape of the client while people are still evaluating. Starting at &lt;code&gt;1.0.0&lt;/code&gt; because it looks committed is how teams end up doing a major bump in month two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who owns the credentials.&lt;/strong&gt; Registry tokens are long-lived and they publish under your organisation's name. They belong in your secret manager with an owner, not in one engineer's shell profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating it
&lt;/h2&gt;

&lt;p&gt;Publishing by hand works exactly until the release where somebody forgets one language, and then you have five SDKs on 2.1.0 and one on 2.0.4, and the one that lagged is the one someone reports a bug against.&lt;/p&gt;

&lt;p&gt;The property worth having is that a release is one action producing every package, with the version coming from a single source rather than from six manual edits. If your version lives in your OpenAPI document and propagates into every generated manifest, the manifests cannot disagree with each other.&lt;/p&gt;

&lt;p&gt;On Octri, connecting each registry account is a one-time step and releases go out on publish, with Go and Swift handled through the GitHub App rather than a registry token. Automatic publishing is a Business-tier feature; on the tiers below it, the packages are generated and you publish them yourself, which is the same artifacts with a manual last step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you press publish
&lt;/h2&gt;

&lt;p&gt;Read the package. Not the code, the packaging: what files are included, whether the README renders, whether the license is there, whether the version matches what you think you are shipping. Every registry makes at least one of those easy to get wrong, and all of them are visible on the page your users land on.&lt;/p&gt;

&lt;p&gt;If you have not decided which languages to ship yet, the &lt;a href="https://octri.dev/openapi-to-typescript-sdk" rel="noopener noreferrer"&gt;OpenAPI to TypeScript SDK&lt;/a&gt; and &lt;a href="https://octri.dev/openapi-to-python-sdk" rel="noopener noreferrer"&gt;OpenAPI to Python SDK&lt;/a&gt; pages show what the generated client looks like at the call site in each. And before any of it, the &lt;a href="https://octri.dev/openapi-audit" rel="noopener noreferrer"&gt;spec audit&lt;/a&gt; tells you whether your document is in a state to generate something worth publishing.&lt;/p&gt;

</description>
      <category>api</category>
      <category>npm</category>
      <category>programming</category>
      <category>openapi</category>
    </item>
    <item>
      <title>How to Generate API Documentation from an OpenAPI Spec (2026 Guide)</title>
      <dc:creator>Octri</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:16:36 +0000</pubDate>
      <link>https://dev.to/octri/how-to-generate-api-documentation-from-an-openapi-spec-2026-guide-706</link>
      <guid>https://dev.to/octri/how-to-generate-api-documentation-from-an-openapi-spec-2026-guide-706</guid>
      <description>&lt;p&gt;Good API documentation decides whether a developer integrates your API in an&lt;br&gt;
afternoon or gives up before lunch. The reliable way to get there is to&lt;br&gt;
&lt;strong&gt;generate your documentation directly from your OpenAPI specification&lt;/strong&gt;. This&lt;br&gt;
guide covers the whole path, from writing the spec to hosting docs that update&lt;br&gt;
themselves.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is an OpenAPI spec?
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;OpenAPI specification&lt;/strong&gt; (formerly Swagger) is a machine-readable document,&lt;br&gt;
written in YAML or JSON, that describes every endpoint your API exposes: paths,&lt;br&gt;
parameters, request bodies, responses, authentication and data models.&lt;br&gt;
Because it is structured data, tools can read it and generate documentation,&lt;br&gt;
client SDKs, mock servers, and tests from a single source of truth.&lt;/p&gt;

&lt;p&gt;If you already have an &lt;code&gt;openapi.yaml&lt;/code&gt; or &lt;code&gt;swagger.json&lt;/code&gt;, you are ready to&lt;br&gt;
generate docs. If not, most modern frameworks can emit one for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why generate docs from the spec instead of writing them by hand?
&lt;/h2&gt;

&lt;p&gt;Hand-written documentation has one fatal flaw: it drifts. Add a field or change&lt;br&gt;
a status code and the prose is wrong. Nobody notices until a customer files a&lt;br&gt;
ticket.&lt;/p&gt;

&lt;p&gt;Generating docs from the spec flips that around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One source of truth.&lt;/strong&gt; The spec describes the API; the docs are derived from
it, so they can never silently disagree with the contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed.&lt;/strong&gt; A complete reference site comes out of one command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency.&lt;/strong&gt; Every endpoint is documented the same way, with the same
structure for parameters, responses, and errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free downstream artifacts.&lt;/strong&gt; The same spec generates SDKs, Postman
collections, and mock servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat your OpenAPI document like source code: review it, lint it, and keep it in version control. Everything downstream inherits its quality.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Step 1: Write and validate your OpenAPI spec
&lt;/h2&gt;

&lt;p&gt;Start from a small, correct spec and grow it. Here is a minimal but valid&lt;br&gt;
example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.1.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invoices API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/invoices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;createInvoice&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create an invoice&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/InvoiceInput"&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;201"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The created invoice.&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Invoice"&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;InvoiceInput&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;integer&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Amount in the smallest currency unit.&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;usd&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;eur&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gbp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;Invoice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;allOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/InvoiceInput"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before generating anything, &lt;strong&gt;validate the spec&lt;/strong&gt; in CI so a broken document can&lt;br&gt;
never ship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @redocly/cli lint openapi.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Choose how you'll generate the docs
&lt;/h2&gt;

&lt;p&gt;There are three broad approaches. Pick based on how much control and automation&lt;br&gt;
you need.&lt;/p&gt;

&lt;p&gt;The first two are fine for a spec that rarely changes. If your API changes every&lt;br&gt;
release, pick an approach that regenerates docs from the spec on each push.&lt;br&gt;
Otherwise you are back to manual drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Generate the documentation
&lt;/h2&gt;

&lt;p&gt;With a validated spec, generating a reference site is a single command with most&lt;br&gt;
tools. The output is a set of static pages describing every endpoint, grouped by&lt;br&gt;
tag, with request/response schemas rendered from your components.&lt;/p&gt;

&lt;p&gt;The quality of that output is bounded by the quality of the spec. That is why&lt;br&gt;
the next section matters more than the tool you pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Host the docs and wire up search
&lt;/h2&gt;

&lt;p&gt;Documentation only helps if developers can find and read it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serve it on a fast, cached URL (a CDN or a static host). On Octri that URL is
live from your first upload, and a
&lt;a href="https://docs.octri.dev/documentation/guides/custom-domains" rel="noopener noreferrer"&gt;custom domain&lt;/a&gt; moves it
to your own hostname.&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;full-text search&lt;/strong&gt; so readers can jump straight to an endpoint. See
&lt;a href="https://docs.octri.dev/documentation/guides/search-and-chat" rel="noopener noreferrer"&gt;search and AI chat&lt;/a&gt;,
which also answers reader questions from your published pages.&lt;/li&gt;
&lt;li&gt;Include copy-paste &lt;strong&gt;code examples&lt;/strong&gt; and, ideally, a live "try it" console.&lt;/li&gt;
&lt;li&gt;Expose an &lt;code&gt;sitemap.xml&lt;/code&gt; and clean canonical URLs so search engines index every
endpoint page. See &lt;a href="https://docs.octri.dev/documentation/guides/seo" rel="noopener noreferrer"&gt;SEO&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Keep the docs in sync automatically
&lt;/h2&gt;

&lt;p&gt;This is the step teams skip, and the reason so many API docs are subtly wrong.&lt;br&gt;
Connect documentation generation to your pipeline so it re-runs whenever the spec&lt;br&gt;
changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A push updates &lt;code&gt;openapi.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CI validates the spec.&lt;/li&gt;
&lt;li&gt;Docs regenerate from the new spec.&lt;/li&gt;
&lt;li&gt;Only the pages that changed are rebuilt and re-published.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once that loop runs, the reference stays current without anyone maintaining it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices for generated API docs
&lt;/h2&gt;

&lt;p&gt;A few habits make a large difference to the generated output. We go deeper on&lt;br&gt;
these in &lt;a href="https://octri.dev/blog/ten-habits-for-writing-great-openapi-specs" rel="noopener noreferrer"&gt;Ten Habits for Writing Great OpenAPI Specs&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give every operation an &lt;code&gt;operationId&lt;/code&gt;.&lt;/strong&gt; It becomes the anchor link and the
SDK method name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write real &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;summary&lt;/code&gt; fields.&lt;/strong&gt; "Returns the authenticated
user's profile" beats "Get user."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;examples&lt;/code&gt;&lt;/strong&gt; to request bodies and responses. They flow straight into
the docs and the try-it console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model your errors explicitly&lt;/strong&gt; with documented &lt;code&gt;4xx&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt; responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse &lt;code&gt;components&lt;/code&gt; with &lt;code&gt;$ref&lt;/code&gt;&lt;/strong&gt; so shared schemas render consistently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tag operations&lt;/strong&gt; into logical groups so the sidebar has a sensible order.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes to avoid
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Documenting only the happy path and omitting error responses.&lt;/li&gt;
&lt;li&gt;Leaving &lt;code&gt;operationId&lt;/code&gt; blank, producing unreadable anchors and SDK names.&lt;/li&gt;
&lt;li&gt;Editing generated HTML by hand. Your changes vanish on the next build.&lt;/li&gt;
&lt;li&gt;Letting the spec and the running API diverge because nothing validates them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is OpenAPI the same as Swagger?
&lt;/h3&gt;

&lt;p&gt;Effectively yes. "Swagger" was the original name; the specification was donated&lt;br&gt;
to the OpenAPI Initiative and renamed &lt;strong&gt;OpenAPI&lt;/strong&gt;. Swagger now refers to a set of&lt;br&gt;
tools built around the OpenAPI spec. You can read the current specification at&lt;br&gt;
&lt;a href="https://spec.openapis.org/" rel="noopener noreferrer"&gt;spec.openapis.org&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I generate SDKs from the same spec?
&lt;/h3&gt;

&lt;p&gt;Yes. The same OpenAPI document that produces your docs can generate typed client&lt;br&gt;
libraries in many languages. SDKs and docs then come from one contract and cannot&lt;br&gt;
disagree. Octri does ten of them, documented in the &lt;a href="https://docs.octri.dev/sdks/guides/overview" rel="noopener noreferrer"&gt;SDK guides&lt;/a&gt;,&lt;br&gt;
and can &lt;a href="https://docs.octri.dev/sdks/guides/publishing" rel="noopener noreferrer"&gt;publish&lt;/a&gt; each one to&lt;br&gt;
its native registry.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I stop my docs from going out of date?
&lt;/h3&gt;

&lt;p&gt;Automate generation in CI so docs regenerate from the spec on every change, and&lt;br&gt;
publish only the pages that changed. Manual rebuilds are where drift&lt;br&gt;
creeps in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do generated docs help with SEO?
&lt;/h3&gt;

&lt;p&gt;They can, if each endpoint gets its own crawlable page with a clean canonical&lt;br&gt;
URL, descriptive titles and a sitemap. That turns your reference into hundreds&lt;br&gt;
of indexable, long-tail landing pages. Octri emits a canonical URL per page and&lt;br&gt;
derives sensible titles on its own; the &lt;a href="https://docs.octri.dev/documentation/guides/seo" rel="noopener noreferrer"&gt;SEO guide&lt;/a&gt;&lt;br&gt;
covers what you can override, including Open Graph images and whether a project is&lt;br&gt;
indexable at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Turn your spec into docs
&lt;/h2&gt;

&lt;p&gt;You don't have to wire this pipeline together yourself. &lt;strong&gt;Octri&lt;/strong&gt; takes your&lt;br&gt;
OpenAPI spec, generates documentation and production-ready SDKs, and keeps both&lt;br&gt;
in sync on every push.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://octri.dev/register" rel="noopener noreferrer"&gt;Create your first project&lt;/a&gt;, or read the&lt;br&gt;
&lt;a href="https://docs.octri.dev/documentation/guides/overview" rel="noopener noreferrer"&gt;docs&lt;/a&gt; to see how it works.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>api</category>
      <category>documentation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Should you hand-write your SDKs or generate them?</title>
      <dc:creator>Octri</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:47:52 +0000</pubDate>
      <link>https://dev.to/octri/should-you-hand-write-your-sdks-or-generate-them-13n8</link>
      <guid>https://dev.to/octri/should-you-hand-write-your-sdks-or-generate-them-13n8</guid>
      <description>&lt;p&gt;The case for hand-writing is better than generation advocates admit, and it has a shape.&lt;/p&gt;

&lt;p&gt;A hand-written client can be idiomatic in ways a generator cannot reach. It can name a method after what your users call the thing rather than after your operationId. It can collapse three endpoints into one convenience call because that is how the workflow actually goes. It can have a helper that does the retry-and-poll dance your API requires and your spec does not describe.&lt;/p&gt;

&lt;p&gt;If you ship one language, your API changes a few times a year, and you have someone who cares about that library, hand-writing produces a better artifact. That is the honest version.&lt;/p&gt;

&lt;p&gt;The problem is that almost nobody stays in that situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually breaks it
&lt;/h2&gt;

&lt;p&gt;Two variables, and they multiply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Languages.&lt;/strong&gt; One library is a craft project. Six is a maintenance obligation, and the sixth is always the one nobody on the team writes daily. Your Rust SDK gets written by whoever was free, and it gets updated at whatever rate that person has time for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change rate.&lt;/strong&gt; An API that changes monthly means every change lands six times. Miss one and you have SDKs that disagree about what your API does, which is worse than having fewer SDKs.&lt;/p&gt;

&lt;p&gt;Multiply them and you get the failure everyone recognises: the TypeScript client is current because that is what the team uses, Python is one version behind, and Ruby has an open issue from March saying a field is missing.&lt;/p&gt;

&lt;p&gt;The 2026 drift data puts numbers on the pressure. 41% of APIs see schema drift within thirty days, and 86% of those events are field additions, which are exactly the changes nobody remembers to propagate because nothing breaks when you skip them. Six hand-written clients means six chances to skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  What generation is actually good at
&lt;/h2&gt;

&lt;p&gt;Not the method names. The parts nobody enjoys writing and everybody gets subtly wrong on the fifth language:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency across languages.&lt;/strong&gt; One retry policy, one pagination contract, one auth mechanism, emitted six times. Teams that hand-write six clients write six retry policies, and by year two they disagree about which status codes are safe to repeat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The unglamorous correctness.&lt;/strong&gt; Backoff with jitter, per-attempt timeouts, idempotency keys on writes, typed errors split by cause. Every client needs these and hand-written ones acquire them incrementally after incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keeping up.&lt;/strong&gt; A field added to a response appears in every language on the next generation, without anyone remembering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reaching languages you do not staff.&lt;/strong&gt; You probably do not have a Dart developer. Your Flutter users still want a client.&lt;/p&gt;

&lt;h2&gt;
  
  
  What generation is bad at, and what to do about it
&lt;/h2&gt;

&lt;p&gt;This is the part worth being straight about, because it is where the objection lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated names can be bad.&lt;/strong&gt; They come from your operationIds, and if those were written for a linter rather than a call site you get &lt;code&gt;client.actions.actionsActionsGet()&lt;/code&gt;. The fix is not to hand-write the client, it is to fix the ids or override the generated method name per endpoint. Both are cheaper than maintaining a library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generators do not know your workflows.&lt;/strong&gt; If using your API means "create, then poll until ready, then fetch", no generator will produce the helper that does it. That helper is real value and it has to be written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated code can be ugly.&lt;/strong&gt; Sometimes true, and it matters less than people think because most consumers read the method signature and the types, not the transport layer.&lt;/p&gt;

&lt;p&gt;The synthesis most teams land on is the useful one: generate the client, hand-write a thin layer of workflow helpers on top of it. The generated part tracks your API automatically and the hand-written part is small enough that one person can own it in every language you care about. You get idiom where idiom matters and coverage everywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision, plainly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hand-write&lt;/strong&gt; if you ship one or two languages, your API is stable, and someone owns the library as part of their job rather than as an occasional errand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate&lt;/strong&gt; if you ship three or more languages, or your API changes more than quarterly, or you want to support a language nobody on your team writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate plus a helper layer&lt;/strong&gt; if your API has multi-step workflows worth wrapping, which is most APIs past a certain size.&lt;/p&gt;

&lt;p&gt;The crossover is lower than instinct suggests, because the cost of a hand-written SDK is not the writing. It is the year of small updates afterwards, paid in the languages you are least equipped to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question that settles it
&lt;/h2&gt;

&lt;p&gt;Ask what happens the next time you add a required parameter to an endpoint.&lt;/p&gt;

&lt;p&gt;If the answer is "I update the spec and every client regenerates", you are fine. If it is "I update the spec, then open six repositories", you already know which of the six will be late, and your users will find out before you do.&lt;/p&gt;

&lt;p&gt;If you want to see what generated output actually looks like before deciding, the &lt;a href="https://octri.dev/openapi-to-typescript-sdk" rel="noopener noreferrer"&gt;OpenAPI to TypeScript SDK&lt;/a&gt;, &lt;a href="https://octri.dev/openapi-to-python-sdk" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, &lt;a href="https://octri.dev/openapi-to-go-sdk" rel="noopener noreferrer"&gt;Go&lt;/a&gt; and &lt;a href="https://octri.dev/openapi-to-java-sdk" rel="noopener noreferrer"&gt;Java&lt;/a&gt; pages show the call site each one produces from the same document, including the default client shape and what you can change about it.&lt;/p&gt;

&lt;p&gt;And whichever way you decide, &lt;a href="https://octri.dev/openapi-audit" rel="noopener noreferrer"&gt;the spec audit&lt;/a&gt; is worth running first, because a document that scores badly produces a bad generated client and a hand-written one built on the same missing schemas will have the same gaps, only hidden behind code somebody wrote.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>MCP vs OpenAPI: they answer different questions</title>
      <dc:creator>Octri</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:38:30 +0000</pubDate>
      <link>https://dev.to/octri/mcp-vs-openapi-they-answer-different-questions-23bl</link>
      <guid>https://dev.to/octri/mcp-vs-openapi-they-answer-different-questions-23bl</guid>
      <description>&lt;p&gt;The question gets asked as though one is replacing the other, and the framing is wrong in a way that leads teams to build the wrong thing.&lt;/p&gt;

&lt;p&gt;OpenAPI is a &lt;strong&gt;description&lt;/strong&gt;. It is a document that says what your API accepts and returns, read by humans, code generators, linters and documentation tools. It is static, it is versioned, and it describes the whole surface whether or not anyone uses it.&lt;/p&gt;

&lt;p&gt;MCP is a &lt;strong&gt;protocol&lt;/strong&gt;. It is a live conversation between an assistant and a server, in which the server offers a set of tools and the assistant calls them while it works. It is dynamic, it is scoped to a session, and it carries only what an agent should be able to do right now.&lt;/p&gt;

&lt;p&gt;One is a blueprint. The other is a set of keys. Asking which to use is like asking whether to have architectural drawings or door access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the comparison keeps happening
&lt;/h2&gt;

&lt;p&gt;Because in practice a lot of MCP servers are generated from OpenAPI documents, which makes them look like two formats for the same thing.&lt;/p&gt;

&lt;p&gt;They are not, and the generation step is doing real work. It is selecting which operations become tools, naming them for an audience that reads names rather than paths, writing descriptions aimed at a model deciding which tool to reach for, and deciding what an agent is allowed to touch. A spec describes 300 endpoints. A good MCP server might expose 30 of them.&lt;/p&gt;

&lt;p&gt;That selection is the product. If your MCP server is a mechanical one-to-one dump of your spec, you have converted a format and skipped the part that mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  What OpenAPI is better at
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Being complete.&lt;/strong&gt; A spec covers every operation including the ones nobody should call from an agent, the deprecated ones, the internal ones. Completeness is the point. It is the contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generating things.&lt;/strong&gt; Documentation, client libraries, mock servers, validation middleware, test scaffolding. Every one of those wants the full typed surface, and none of them wants a curated subset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being reviewed.&lt;/strong&gt; A spec sits in a pull request. Someone can see that an operation gained a required parameter. There is no equivalent review surface for "what my agent can do today" unless you build one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Living without a runtime.&lt;/strong&gt; A spec is a file. It works when nothing is running, which is how a developer reads your API at 2am before deciding to integrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP is better at
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Being current.&lt;/strong&gt; An agent asking a live server gets the answer as of now. A spec pasted into a context window is a snapshot that started going stale the moment it was pasted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being selective.&lt;/strong&gt; Context is finite and expensive. A protocol that lets an agent ask for one endpoint's detail beats one that requires it to hold a megabyte of JSON to answer a question about one call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executing.&lt;/strong&gt; This is the part with no OpenAPI equivalent. A spec tells an agent what a request would look like. A tool call makes the request and returns the response, which collapses the loop between writing code and finding out whether it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Carrying the things a spec cannot.&lt;/strong&gt; Your written guides, your changelog, the real method names in your published SDK. None of that is in your OpenAPI document, and all of it changes what an agent writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The relationship in one line
&lt;/h2&gt;

&lt;p&gt;Your OpenAPI document should be the source both come from, and your MCP server should be a curated projection of it.&lt;/p&gt;

&lt;p&gt;That ordering matters. Teams that hand-build an MCP server separately end up with two definitions of their API that drift, and the agent-facing one drifts faster because nothing compiles against it. Teams that generate the tools from the spec get one source of truth and a curation layer on top.&lt;/p&gt;

&lt;p&gt;The curation layer is not optional, and it is where the judgement lives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which operations become tools.&lt;/strong&gt; Exclude anything irreversible, anything administrative, anything an agent has no business calling. The exclusion list you wrote for your public SDK is usually close to right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the tools are called.&lt;/strong&gt; A tool name is read by a model choosing between options. &lt;code&gt;create_invoice&lt;/code&gt; is a choice. &lt;code&gt;postV1InvoicesCreate&lt;/code&gt; is a transport detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the descriptions say.&lt;/strong&gt; This is the highest-leverage text in the whole system and it is usually copied from a spec summary written for a documentation page. A tool description is a decision aid: when should the model use this instead of the other one.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you need only one
&lt;/h2&gt;

&lt;p&gt;You need only OpenAPI if nothing is calling your API through an assistant. That is a shrinking set, and it is a real set.&lt;/p&gt;

&lt;p&gt;You need only MCP if you are exposing something that is not an HTTP API at all, which is the case for plenty of MCP servers: file systems, databases, internal services with no public contract. There is no spec to generate from, and hand-writing the tools is correct.&lt;/p&gt;

&lt;p&gt;For an HTTP API with an OpenAPI document, wanting one and not the other is almost always a sequencing question rather than a choice. The spec comes first because everything else derives from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical test
&lt;/h2&gt;

&lt;p&gt;Ask what happens when you add an endpoint.&lt;/p&gt;

&lt;p&gt;If your documentation, your client libraries and your agent tools all update from that one change, the relationship is right. If adding an endpoint means editing a spec and then separately editing an MCP server, you have two sources of truth and you will find out which one is stale at the worst moment.&lt;/p&gt;

&lt;p&gt;That is the whole argument for generating both from one document, and it is why &lt;a href="https://octri.dev" rel="noopener noreferrer"&gt;Octri&lt;/a&gt; treats the spec as the input and the docs, the SDKs and the MCP server as outputs of it. One change, four surfaces, no drift between them.&lt;/p&gt;

&lt;p&gt;If you want the mechanics of the second half, &lt;a href="https://octri.dev/openapi-to-mcp-server" rel="noopener noreferrer"&gt;generating an MCP server from an OpenAPI spec&lt;/a&gt; covers the setup. And if you are not sure your spec is in a state to generate anything useful yet, the &lt;a href="https://octri.dev/openapi-audit" rel="noopener noreferrer"&gt;spec audit&lt;/a&gt; scores it against the fourteen rules that decide, free and without an account.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>openapi</category>
      <category>ai</category>
      <category>api</category>
    </item>
  </channel>
</rss>
