Skip to content
Published on

API Schema Landscape 2026 — JSON Schema, OpenAPI 3.1, AsyncAPI, GraphQL, gRPC, Smithy, TypeSpec in One Map

Authors

Prologue — schemas now come before code

A 2026 backend meeting usually opens with one sentence: "Don't sketch the endpoint yet — show me the schema."

There was an era where you wrote the handler first and then auto-generated an OpenAPI document out of it. Spec-from-code — the code was truth, the spec was a byproduct. It felt easy. It also broke. Two teams guessed at each other's response shapes and produced month-long integration bugs, client SDKs were always one step behind, and the internal "current API" wiki page was half lies.

2026 inverted that. Spec-first — write the schema first, then generate types, docs, server stubs, client SDKs, test fixtures and AI tool definitions out of it. The truth lives in a schema file.

So this article maps it out. "API" looks like one word but the right tool changes with the protocol. REST uses OpenAPI 3.1. Events use AsyncAPI 3.0. Service-to-service uses Protobuf. Frontend-friendly graphs use GraphQL. The grammar underneath all of them is JSON Schema 2020-12. Then add AWS's Smithy, Microsoft's TypeSpec, the contract-testing tool Pact — many names, but the roles are crisp.

This piece is the 2026 schema atlas. The identity and strengths of each format, the same domain model rendered in four, the new role JSON Schema took on inside AI tool calling, and an honest decision frame for "what should my team actually use."


1. JSON Schema 2020-12 — the foundation of everything

One line first: JSON Schema is a standard for describing the shape of a JSON value in more JSON. "This field is an integer, this one is between 1 and 120, this one is an ISO 8601 date" — those constraints written as declarations.

Draft 2020-12 is the de-facto stable baseline. Work after it continues, but as of 2026 the version with the deepest compatibility and tooling is 2020-12. OpenAPI 3.1 aligned fully with this draft, and the tiny historic frictions (the old nullable/example differences that used to bite us) are gone.

Why JSON Schema matters in 2026: it shows up in three different jobs at once.

  1. REST APIs: the inside of OpenAPI 3.1's components.schemas is literally JSON Schema 2020-12.
  2. AI tool calling (function calling): the tool definition formats of OpenAI, Anthropic and Gemini are all JSON Schema subsets. The model returns arguments filled in as JSON, and the host validates them against the same schema.
  3. Config validation: the standard you most often see when validating a kubeconfig, a CI workflow, or an app config file.

JSON Schema is not a format, it is a grammar. Learn it once and you use it in all three places. It is arguably the single standard a backend engineer touches most often in 2026.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/user.json",
  "type": "object",
  "required": ["id", "email"],
  "properties": {
    "id": { "type": "string", "format": "uuid" },
    "email": { "type": "string", "format": "email" },
    "age": { "type": "integer", "minimum": 0, "maximum": 130 },
    "roles": {
      "type": "array",
      "items": { "type": "string", "enum": ["admin", "member", "guest"] },
      "uniqueItems": true
    }
  },
  "additionalProperties": false
}

This small file slots into OpenAPI, into an AI tool definition, into a standalone validator. The reusable unit is the truth.


2. OpenAPI 3.1 — the REST standard is settled, and only just starting

The headline difference between OpenAPI 3.0 and 3.1 is one line. 3.1 aligned fully with JSON Schema 2020-12. The 3.0-era nullable: true is gone — the standard is "type": ["string", "null"]. The semantics of example/examples are cleaned up. $ref got freer — it can point at any JSON Schema.

In 2026 OpenAPI 3.1 is the de-facto standard for REST API specs. The Swagger-era residue mostly washed out, and new projects almost universally start at 3.1. OpenAPI 3.2 is in motion (tidying smaller surfaces around forms, headers, multi-content responses), but 3.1 and 3.2 evolve without breaking each other. Starting at 3.1 today is safe.

openapi: 3.1.0
info:
  title: Orders API
  version: 1.0.0
paths:
  /users/{userId}/orders:
    get:
      summary: List orders for a user
      parameters:
        - name: userId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Order'
components:
  schemas:
    Order:
      type: object
      required: [id, userId, total]
      properties:
        id:    { type: string, format: uuid }
        userId:{ type: string, format: uuid }
        total: { type: number, minimum: 0 }
        status:
          type: string
          enum: [pending, paid, shipped, cancelled]

What gets generated out of this single file: TypeScript/Go/Python client SDKs, server stubs, Postman/Bruno collections, interactive docs, mock servers, contract-test fixtures, AI tool definitions an agent can call. That is the 2026 workflow — one spec, every artifact.

Swagger UI still runs, but the new defaults are Stoplight Elements and Scalar. Both treat OpenAPI 3.1 as native, both render dark mode, search, "try it" and code samples far more cleanly. Scalar in particular spread fast because of its one-script-tag embed. Swagger UI is now an optional choice, not the default.


3. AsyncAPI 3.0 — the OpenAPI of events

REST has OpenAPI. So what about Kafka topics, MQTT channels, WebSocket streams, Server-Sent Events? The 2026 answer is crisp — AsyncAPI 3.0.

The loudest critique of AsyncAPI 2.x was the ambiguity of "channel equals topic equals operation bundle." Publish and subscribe were tangled inside the same channel definition. 3.0 separated those into explicit operations. A channel is the path the messages flow over, an operation is the act of sending or receiving. That single split alone made systems like Kafka much cleaner to model.

The core vocabulary of AsyncAPI 3.0:

  • channels — the paths messages flow over (topic, queue, channel).
  • operations — what happens on a channel (send, receive). Explicit.
  • messages — the payload schemas (reusing JSON Schema directly).
  • servers — Kafka brokers, MQTT brokers, WebSocket endpoints — protocol-specific bindings.
asyncapi: 3.0.0
info:
  title: Orders Events
  version: 1.0.0
servers:
  kafka-prod:
    host: kafka.prod.example.com:9092
    protocol: kafka
channels:
  orderCreated:
    address: orders.created.v1
    messages:
      OrderCreated:
        $ref: '#/components/messages/OrderCreated'
operations:
  publishOrderCreated:
    action: send
    channel: { $ref: '#/channels/orderCreated' }
components:
  messages:
    OrderCreated:
      payload:
        type: object
        required: [orderId, userId, createdAt]
        properties:
          orderId:   { type: string, format: uuid }
          userId:    { type: string, format: uuid }
          createdAt: { type: string, format: date-time }
          total:     { type: number, minimum: 0 }

Two reasons AsyncAPI 3.0 settled in 2026. First, event-driven systems are not a special case anymore — almost every microservices shop runs Kafka, NATS or MQTT. Second, the AsyncAPI ecosystem stopped imitating OpenAPI and grew its own tools — Studio, generator (server stubs, clients), validator, catalog. The usability gap mostly closed.

When REST and events coexist in the same system — which is normal in 2026 — the standard pattern is to keep OpenAPI and AsyncAPI side by side in one repo. Both stand on JSON Schema, so sharing message payload schemas across them is easy.


4. GraphQL SDL — the peace treaty with the frontend

GraphQL was published by Facebook in 2015, moved to the GraphQL Foundation in 2019, and as of 2026 still holds a strong niche. Especially where many clients need different shapes of data — BFFs, content platforms with several mobile apps, dashboards.

GraphQL schemas are written in their own DSL — SDL (Schema Definition Language). Unlike OpenAPI, AsyncAPI and JSON Schema, it is not JSON.

type User {
  id: ID!
  email: String!
  age: Int
  orders(status: OrderStatus, limit: Int = 20): [Order!]!
}

type Order {
  id: ID!
  total: Float!
  status: OrderStatus!
  createdAt: DateTime!
}

enum OrderStatus {
  PENDING
  PAID
  SHIPPED
  CANCELLED
}

type Query {
  user(id: ID!): User
  orders(userId: ID!, status: OrderStatus): [Order!]!
}

scalar DateTime

GraphQL's charm is introspection — the schema itself is queryable from the API. Client tools point at a live server, pull the schema, and codegen typed hooks. Apollo Studio, GraphiQL, GraphQL Code Generator, urql, Relay — the toolbox is thick.

2026 GraphQL trends:

  • Federation v2 is settled — the standard for composing many services' GraphQLs into a single supergraph.
  • Persisted queries became the security and performance default — clients ship a hash of a build-time-registered query, not arbitrary text.
  • Coexistence with REST. The "rewrite everything in GraphQL" wave is over. A common pattern: GraphQL where the frontend churns, gRPC/REST between backends.

Be honest about the weaknesses. Caching is awkward — one URL means HTTP caches do not help much. N+1 must be solved with patterns like DataLoader. Without query cost analysis, a single client can knock the server over. And it does not fit every place — for a simple CRUD API, OpenAPI is almost always simpler.


5. gRPC plus Protobuf — still the default between services

Service-to-service in 2026? gRPC plus Protobuf is still the default. The reasons are simple — small, fast, polyglot, streaming is first-class.

Protobuf is a binary schema. Define messages and services in a .proto file, and protoc (or more often Buf) generates per-language code. On the wire it is far smaller than JSON (field names compress into integer tags) and far faster to parse.

syntax = "proto3";

package shop.v1;

import "google/protobuf/timestamp.proto";

message User {
  string id    = 1;
  string email = 2;
  int32  age   = 3;
  repeated string roles = 4;
}

message Order {
  string id      = 1;
  string user_id = 2;
  double total   = 3;
  OrderStatus status = 4;
  google.protobuf.Timestamp created_at = 5;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING     = 1;
  ORDER_STATUS_PAID        = 2;
  ORDER_STATUS_SHIPPED     = 3;
  ORDER_STATUS_CANCELLED   = 4;
}

service OrdersService {
  rpc GetOrders(GetOrdersRequest) returns (GetOrdersResponse);
  rpc StreamOrders(stream GetOrdersRequest) returns (stream Order);
}

message GetOrdersRequest  { string user_id = 1; }
message GetOrdersResponse { repeated Order orders = 1; }

The 2026 center of gravity for the Protobuf ecosystem is Buf. buf build, buf lint, buf breaking, buf generate — the entire usability gap of raw protoc is closed. Buf Schema Registry plays the role of "GitHub for protobuf schemas" — versions, releases, dependencies.

When you want gRPC:

  • Service-to-service — the internal mesh.
  • Bidirectional streaming — chat, live data, game servers.
  • High performance where it really matters — very high RPS, big payloads.

When you do not:

  • Direct from the browser — gRPC-Web exists, but it is awkward. REST or GraphQL is friendlier.
  • Public third-party APIs — too high a bar for outside developers.
  • Debug by curl — the wire is binary, you cannot just curl | jq it.

A notable trend: Connect (the protocol Buf authored) lets the same .proto expose both gRPC and REST/JSON. Internal mesh in gRPC, external clients hitting the same handlers in JSON — one codebase, two birds.


6. Same model, four formats — User-with-orders

Stop comparing abstractly and move the same domain across formats. A user owns an array of orders.

6.1 JSON Schema (standalone)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "Order": {
      "type": "object",
      "required": ["id", "total", "status"],
      "properties": {
        "id":     { "type": "string", "format": "uuid" },
        "total":  { "type": "number", "minimum": 0 },
        "status": { "type": "string", "enum": ["pending","paid","shipped","cancelled"] }
      }
    }
  },
  "type": "object",
  "required": ["id", "email", "orders"],
  "properties": {
    "id":    { "type": "string", "format": "uuid" },
    "email": { "type": "string", "format": "email" },
    "orders":{ "type": "array", "items": { "$ref": "#/$defs/Order" } }
  }
}

6.2 OpenAPI 3.1 (REST)

paths:
  /users/{id}:
    get:
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          content:
            application/json:
              schema: { $ref: '#/components/schemas/UserWithOrders' }
components:
  schemas:
    UserWithOrders:
      type: object
      required: [id, email, orders]
      properties:
        id:    { type: string, format: uuid }
        email: { type: string, format: email }
        orders:
          type: array
          items: { $ref: '#/components/schemas/Order' }
    Order:
      type: object
      required: [id, total, status]
      properties:
        id:     { type: string, format: uuid }
        total:  { type: number, minimum: 0 }
        status: { type: string, enum: [pending, paid, shipped, cancelled] }

6.3 GraphQL SDL

type User {
  id: ID!
  email: String!
  orders: [Order!]!
}

type Order {
  id: ID!
  total: Float!
  status: OrderStatus!
}

enum OrderStatus { PENDING PAID SHIPPED CANCELLED }

type Query {
  user(id: ID!): User
}

6.4 Protobuf

message User {
  string id    = 1;
  string email = 2;
  repeated Order orders = 3;
}

message Order {
  string id     = 1;
  double total  = 2;
  OrderStatus status = 3;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING     = 1;
  ORDER_STATUS_PAID        = 2;
  ORDER_STATUS_SHIPPED     = 3;
  ORDER_STATUS_CANCELLED   = 4;
}

Same model, four expressions. The information content is roughly equal, but the audience differs. JSON Schema is the shared grammar, OpenAPI is the HTTP surface, GraphQL is client-friendly, Protobuf is wire-efficient. Picking a tool is not about the shape of the data, it is about who you are talking to.


7. Smithy — AWS's API IDL

Smithy is the API definition language built by AWS. It started as an internal tool for generating AWS's own SDKs, but since the late 2020s it has been published outward and is actively evolving.

Smithy's philosophy is "protocol-agnostic model." You define a model once in .smithy files, then project it onto several protocols — REST (JSON over HTTP), AWS-specific protocols, gRPC. OpenAPI is an output.

$version: "2.0"
namespace shop

service Orders {
    version: "2026-05-14"
    operations: [GetUser]
}

@http(method: "GET", uri: "/users/{userId}")
operation GetUser {
    input := {
        @httpLabel
        @required
        userId: String
    }
    output := {
        @required
        id: String
        @required
        email: String
        @required
        orders: OrderList
    }
}

list OrderList { member: Order }

structure Order {
    @required
    id: String
    @required
    total: Double
    @required
    status: OrderStatus
}

enum OrderStatus {
    PENDING
    PAID
    SHIPPED
    CANCELLED
}

Strengths of Smithy:

  • A trait system more expressive than OpenAPI — auth, pagination, idempotency and other cross-cutting concerns expressed cleanly.
  • All AWS SDKs (JavaScript, Python, Go, Rust and more) are generated from Smithy — industrial validation.
  • One model, many outputs — OpenAPI, client SDKs, server stubs all out of one source.

Weakness: the ecosystem is narrower than OpenAPI's. Adoption outside AWS is growing, but it does not reach OpenAPI 3.1's universality. Unless your story really is "we are publishing SDKs across many languages," OpenAPI is often enough.


8. TypeSpec — Microsoft's authoring layer over OpenAPI

Writing OpenAPI 3.1 YAML by hand is — honestly — long, repetitive and easy to typo. Microsoft's answer to that pain is TypeSpec (formerly Cadl).

TypeSpec is a compact DSL (TypeScript-flavored) for authoring APIs, from which you generate OpenAPI (or JSON Schema, or Protobuf). It is a level above raw OpenAPI YAML on authorability, reuse and tooling.

import "@typespec/http";
using TypeSpec.Http;

@service({ title: "Orders API" })
namespace ShopApi;

model Order {
  id: string;
  total: float64;
  status: OrderStatus;
}

enum OrderStatus { Pending, Paid, Shipped, Cancelled }

model User {
  id: string;
  email: string;
  orders: Order[];
}

@route("/users/{userId}")
interface Users {
  @get op getUser(@path userId: string): User;
}

Far shorter than the equivalent OpenAPI YAML. And it compiles — the TypeSpec compiler emits OpenAPI 3.1, JSON Schema 2020-12, Protobuf and client SDKs in one shot. Most new Azure services are authored in TypeSpec.

Use TypeSpec when:

  • The API is big and complex — dozens of endpoints, many shared models and response patterns.
  • You need multiple outputs from one definition — OpenAPI plus Protobuf plus clients.
  • The team is fluent in TypeScript and the DSL feels native.

Avoid TypeSpec when:

  • The API is small — a single OpenAPI YAML may simply be simpler.
  • You want the toolchain to stay tiny — TypeSpec is another build step.

9. Pact — contract testing, the second use of schemas

Everything so far is documentation plus code generation. There is another strong use of schemas — consumer-driven contract testing. The flagship is Pact.

Setup: service A calls service B. If B reshapes its responses, A breaks. "Integration tests" mean spinning both up — slow and flaky. Pact takes a different route.

Consumer-driven contracts. A (the consumer) writes a contract file that says "I send B this request, I expect this shape back." The contract is uploaded to a Pact Broker. B (the provider) verifies in CI that its real code satisfies that contract. If B reshapes its response — and A depends on it — B's CI goes red immediately.

// consumer-side test
provider
  .uponReceiving('a request for a user')
  .withRequest({ method: 'GET', path: '/users/123' })
  .willRespondWith({
    status: 200,
    body: { id: '123', email: 'a@b.com', orders: eachLike({ id: like('o1'), total: like(99.0) }) },
  })

Pact does not compete with OpenAPI/AsyncAPI — it complements them. OpenAPI is the definition of what the API should be. Pact is the evidence of what a consumer actually depends on. More and more teams ship both.


10. AI tool calling — a second spring for JSON Schema

In 2024 and 2025, as LLMs started calling tools, JSON Schema had a second spring. The tool definition formats of OpenAI, Anthropic and Gemini are all JSON Schema subsets. The model returns arguments filled in as JSON, and the host validates.

Same User-with-orders domain, expressed as a tool that "fetches a user's orders":

{
  "name": "get_user_orders",
  "description": "Return all orders for the given user",
  "input_schema": {
    "type": "object",
    "required": ["user_id"],
    "properties": {
      "user_id": { "type": "string", "format": "uuid" },
      "status": {
        "type": "string",
        "enum": ["pending","paid","shipped","cancelled"]
      },
      "limit":  { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
    },
    "additionalProperties": false
  }
}

Notice this is almost identical to a components.schemas entry inside OpenAPI. The clean 2026 workflow looks like this:

  1. Keep an OpenAPI 3.1 file as the source of truth.
  2. Generate server stubs and client SDKs from the same spec.
  3. Map the spec's components.schemas to AI tool definitions — one handler exposed to both humans and models.
  4. Validate the model's tool-call arguments against the same schema — wrong shape, immediate rejection.

"API" and "AI agent tool" sharing the same schema is the common reality of 2026. That is exactly why a solid grip on JSON Schema 2020-12 became a base skill for backend engineers.


11. Spec-first vs code-first — the debate that did not end

The OpenAPI/AsyncAPI era split into two camps.

Code-first. Write code (decorators, types) first, generate the spec from it. FastAPI, NestJS, tRPC are the flagships. Upside: a single source of truth — the code is the spec. Downside: the spec becomes a shadow of the code, slower for non-technical stakeholder review, and constrained by what the language expresses well.

Spec-first. Write the spec first, generate server stubs and clients out of it. OpenAPI YAML, TypeSpec, Smithy are the flagships. Upside: the protocol specification exists before the code — designers, QA and external clients see it together. Downside: truth can slip between two places — spec and handler drift.

The 2026 balance:

  • External-facing APIs, APIs with many clients (languages, teams) → spec-first is almost always better.
  • Internal single service, rapid prototyping, one fullstack teamcode-first is faster.
  • Hybrid is fine: start code-first, then freeze into spec-first once the spec stabilizes.

Tooling has narrowed the gap. High-level spec-first tools like TypeSpec and Smithy cut the authoring friction; flagship code-first tools like FastAPI tightened spec-conformance checks. Camp warfare is less interesting than asking "how long-lived is this API and how many clients will it have."


12. Toolchain — the 2026 standard stack

AreaNear-standard tooling
OpenAPI doc UIScalar, Stoplight Elements (Swagger UI is legacy)
OpenAPI editorStoplight Studio, Insomnia, Postman
Client codegenopenapi-typescript, oapi-codegen, openapi-generator
Mock serversPrism (Stoplight), Mockoon
Contract testingPact (Pact Broker, PactFlow)
Protobuf toolchainBuf (lint, breaking-change, BSR), protoc (low level)
Protobuf to RESTConnect (Buf), gRPC-Gateway
AsyncAPI editorAsyncAPI Studio
AsyncAPI codegenAsyncAPI Generator
GraphQL stackApollo Studio, GraphiQL, GraphQL Code Generator, Hasura, Relay
TypeSpectsp CLI, VS Code extension
Smithysmithy build, IntelliJ plugin

The era of Swagger UI as the default is over. Stoplight Elements and Scalar both handle OpenAPI 3.1 cleanly, with much better dark mode, search, code samples and theming. On a new project, a single Scalar embed snippet is usually the entire docs page.


13. Decision frame — what should your team use

Same word "API," different audience, different answer.

Q1: Who is the audience?
  ├─ External developers (public API)        → OpenAPI 3.1 + Scalar/Stoplight
  ├─ Internal frontend (many shapes needed)  → GraphQL (Apollo Federation)
  ├─ Internal service-to-service             → gRPC + Protobuf (Buf)
  ├─ Events (Kafka, MQTT, WebSocket)         → AsyncAPI 3.0
  └─ AI agent's tools                        → JSON Schema 2020-12 (reuse from OpenAPI)

Q2: How long-lived is the API?
  ├─ Short prototype       → code-first (FastAPI etc.) for speed
  └─ Long-lived, external  → spec-first (OpenAPI/TypeSpec/Smithy) for solidity

Q3: Do you need multiple outputs from one definition?
  ├─ Yes (REST + gRPC + client SDKs) → TypeSpec or Smithy
  └─ No                              → plain OpenAPI is enough

Q4: How afraid are you of breaking clients?
  ├─ Very → add Pact contract tests
  └─ Modestly → spec plus codegen is enough

A few combinations to avoid in 2026:

  • Starting a new project on OpenAPI 2.0 (Swagger 2) — it is a decade-old spec. Use 3.1.
  • Exposing gRPC directly to the browser — go through gRPC-Web/Connect or a BFF.
  • Calling protoc directly — Buf wins on almost every axis.
  • Treating Swagger UI as the new default — in 2026 it is Scalar or Stoplight Elements.
  • Allowing arbitrary GraphQL queries from external clients — whitelist via persisted queries.

14. Migration — three common paths

A. OpenAPI 3.0 → 3.1

The most common migration. Compatibility is high, so most of it is mechanical:

  1. nullable: true"type": ["string", "null"] (or anyOf with null).
  2. exampleexamples (a single scalar can sometimes stay).
  3. Audit tool-specific vendor extensions (x- keys).
  4. Add an OpenAPI 3.1 validator in CI.

B. Single OpenAPI → OpenAPI plus AsyncAPI split

In a repo where REST and events were tangled in one file, split events into AsyncAPI:

  1. Extract Kafka topics and messages into an AsyncAPI 3.0 file.
  2. Pull message payload schemas into shared JSON Schema files that both sides $ref.
  3. Keep openapi.yaml, asyncapi.yaml and schemas/ side by side in one repo.

C. Hand-written OpenAPI → TypeSpec/Smithy

When the API grows large and repetitive:

  1. Import the existing OpenAPI into TypeSpec/Smithy (official converters exist).
  2. Lift cross-cutting models and traits (auth, pagination, idempotency).
  3. Promote TypeSpec/Smithy to the source of truth, demote OpenAPI to a generated artifact.

All three are incremental, not big-bang. A common 2026 mistake is "let's redo all APIs at once" — it almost always fails. Move one area (one service, one topic), solidify the pattern, then expand.


15. Trust — making the spec actually hold

A schema is only truth if it is enforced. Common 2026 guardrails:

  1. CI lint for OpenAPI/AsyncAPI — Spectral and Redocly are standard. Style rules, banned patterns, required fields.
  2. Breaking-change detection — Buf's buf breaking for protobuf, oasdiff for OpenAPI. A spec-breaking PR turns red.
  3. Validate real responses against the spec — pipe a slice of live traffic into a validator (Optic, prism --validate).
  4. Contract testing (Pact) — captures only the shapes a consumer actually depends on.
  5. Argument validation in AI tool calls — reject malformed model output and ask the model to retry.

You do not need all five, but you need one. "Having a spec" and "the spec being honored" are not the same job.


Epilogue — the schema is the truth

A one-line summary of 2026 backend engineering: the unit of an API is the schema, not the endpoint.

Out of one good schema file flow all of the following: docs, clients, server stubs, test fixtures, AI tool definitions. A good schema tells external developers "what exists right now," internal teams "what cannot break," and AI agents "how to call you."

There are many tools — JSON Schema, OpenAPI 3.1, AsyncAPI 3.0, GraphQL, Protobuf, Smithy, TypeSpec, Pact. It is not that you have more names to memorize; it is that the roles split apart. No single tool wins every situation. A good backend engineer picks the tool by the audience.

14-item checklist

  1. New projects start on OpenAPI 3.1 (not 2.0, not 3.0).
  2. REST (OpenAPI) and events (AsyncAPI) live side by side in the same repo.
  3. Message payload schemas live as shared JSON Schema files that both sides $ref.
  4. Docs are Scalar or Stoplight Elements, not Swagger UI.
  5. Spec lint (Spectral and friends) runs in CI for both OpenAPI and AsyncAPI.
  6. Breaking-change detection runs in CI (buf breaking, oasdiff).
  7. Client SDKs are generated from the spec.
  8. Server stubs and types are generated from the spec (no hand-coding).
  9. The Protobuf toolchain is Buf — protoc is not called by hand.
  10. gRPC is not exposed directly to the browser (Connect/gRPC-Web/BFF instead).
  11. AI tool definitions reuse the same JSON Schema components as the HTTP API.
  12. AI tool-call arguments are validated against the schema before execution.
  13. Critical integrations have Pact (or equivalent contract testing).
  14. GraphQL exposes persisted queries, not arbitrary user queries.

Ten anti-patterns

  1. Maintaining code and spec both by hand — they always drift.
  2. GraphQL everywhere — for plain CRUD, OpenAPI is simpler.
  3. gRPC everywhere — friction is high for browsers and outside developers.
  4. Having an OpenAPI that is never validated — that is a wiki page, not a spec.
  5. Forcing events into OpenAPI — AsyncAPI 3.0 is the right answer.
  6. Starting fresh on Swagger 2 — it is a decade-old spec.
  7. Calling protoc directly — Buf wins everywhere.
  8. Forcing Smithy when you are not shipping multi-language SDKs — ecosystem cost is too high.
  9. Keeping AI tool definitions separate from the OpenAPI components — reuse the same shapes.
  10. Big-bang migration of every API to spec-first at once — incremental or fail.

Next-post candidates

Candidates: Connect protocol deep dive — Buf's fusion of gRPC and REST, Building an internal event catalog with AsyncAPI 3.0, Designing AI tool-call schemas — making JSON Schema model-friendly.

"Not the endpoint, the schema. A good 2026 API is not hand-coded — it falls out of the schema."

— API schema atlas 2026, end.

참고 / References