Back to blog
|14 min read|Docsio

OpenAPI Specification Example: 4 Real Specs You Can Copy

openapi specification exampleopenapiswaggerapi documentation
OpenAPI Specification Example: 4 Real Specs You Can Copy

An OpenAPI specification example is a complete, valid OpenAPI 3.x file you can paste into Swagger UI, Redoc, or any spec parser and have it render straight away. This page collects four of them: a minimal one-endpoint API, a REST CRUD service for /users, an authenticated API with OAuth2 plus API key, and a webhook callback definition. Every file below is valid OpenAPI 3.0.3 or 3.1.0 YAML. Copy any block, save it as openapi.yaml, and load it in your favorite tool. For the conceptual side, the complete guide to OpenAPI documentation explains what the spec is and why it matters before you wire one up.

Key takeaways

  • An OpenAPI specification example must include four required root keys: openapi, info, and one of paths or components. Everything else is optional.
  • YAML is preferred for hand-authored specs; JSON is preferred for machine-generated ones. Both are equally valid.
  • The four examples below cover the patterns most teams need: minimal, CRUD, secured, and webhooks.
  • Tools like Swagger UI, Redoc, Stoplight, and Docsio's AI doc generator turn any of these YAML files into a hosted developer portal.

If you just need a starting point you can adapt, jump straight to the REST CRUD example below. Otherwise the next section gives you the structure first so the YAML reads cleanly.

What is an OpenAPI specification?

An OpenAPI specification is a machine-readable description of a REST API written in YAML or JSON. It defines every endpoint, request shape, response shape, authentication method, and error model your API exposes, in a format any tool can parse. The spec is governed by the OpenAPI Initiative under the Linux Foundation and is currently at version 3.1.0, which aligns the JSON Schema dialect with the latest IETF draft.

The format started life as Swagger in 2011 and was donated to the Linux Foundation in 2015. Today, 93% of developers still ship REST APIs (Postman State of the API, 2025), which keeps OpenAPI as the default contract for nearly every public and internal HTTP API.

A working spec acts as a single source of truth. Frontend teams generate TypeScript clients from it. QA generates Postman collections. Documentation tools render it as a portal. AI tools read it to answer questions about your API. Get the spec right and the rest falls out of it.

Anatomy of an OpenAPI specification

Every OpenAPI specification example you will see below shares the same five top-level sections. Knowing what each one does makes the YAML feel less like an alphabet soup.

  • openapi: the version of the spec format itself. Use 3.0.3 for maximum tool support, 3.1.0 for newer JSON Schema features.
  • info: metadata about your API: title, version, description, contact, license. Required.
  • servers: base URLs where the API is reachable. Optional but strongly recommended.
  • paths: every endpoint, keyed by URL pattern, then by HTTP method. This is where most of your spec lives.
  • components: reusable schemas, parameters, responses, and security schemes. Define a model once, reference it everywhere with $ref.

A spec must include openapi, info, and one of paths or components. Everything else is optional. With those rules in mind, here is the smallest valid OpenAPI specification example.

OpenAPI specification example: a minimal API

This is the smallest spec that still does something useful. One endpoint, one response, no auth. If Swagger UI loads this without errors, your YAML formatting is correct.

openapi: 3.0.3
info:
  title: Hello API
  version: 1.0.0
  description: The smallest valid OpenAPI specification example.
servers:
  - url: https://api.example.com/v1
paths:
  /hello:
    get:
      summary: Return a greeting
      operationId: getHello
      responses:
        '200':
          description: A friendly greeting
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Hello, world
              example:
                message: Hello, world

Save that as openapi.yaml, drop it into editor.swagger.io, and you get a rendered reference page with a "Try it out" button. That is the floor. Real APIs add models, parameters, and authentication, which is what the next three examples cover.

OpenAPI specification example: REST CRUD API

The pattern below is the one most engineers actually need: full CRUD on a /users resource with shared schemas in components. This is the spec you should copy first when starting a new service. It demonstrates path parameters, request bodies, multiple response codes, and $ref reuse, all of which line up with API reference documentation best practices.

openapi: 3.0.3
info:
  title: Users API
  version: 1.2.0
  description: REST CRUD OpenAPI specification example for a users resource.
  contact:
    name: API Support
    email: api@example.com
servers:
  - url: https://api.example.com/v1
paths:
  /users:
    get:
      summary: List users
      operationId: listUsers
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        '200':
          description: A page of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  next_cursor:
                    type: string
                    nullable: true
    post:
      summary: Create a user
      operationId: createUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
  /users/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get user by id
      operationId: getUser
      responses:
        '200':
          description: A single user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      summary: Replace a user
      operationId: replaceUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
    delete:
      summary: Delete a user
      operationId: deleteUser
      responses:
        '204':
          description: Deleted
components:
  schemas:
    User:
      type: object
      required: [id, email, created_at]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
        created_at:
          type: string
          format: date-time
    UserCreate:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        name:
          type: string
  responses:
    BadRequest:
      description: Validation failed
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: invalid_email
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: not_found

A few details that matter. Path parameters are declared once on the path object, not repeated on every method. The UserCreate schema is separate from User because clients should not send id or created_at. Reusable error responses live under components.responses and get $ref'd into each operation, which keeps the spec roughly half its naive size. If you want a real Petstore-style reference to compare against, the OpenAPI Initiative hosts one at learn.openapis.org.

OpenAPI specification example: authenticated API

Most production APIs use either an API key or OAuth2. The next OpenAPI specification example shows both, plus a structured error response. Copy this when your service is past the prototype stage.

openapi: 3.0.3
info:
  title: Billing API
  version: 2.0.0
  description: Authenticated OpenAPI specification example with OAuth2 and API key.
servers:
  - url: https://api.example.com/v2
security:
  - oauth2: [read, write]
  - apiKey: []
paths:
  /invoices:
    get:
      summary: List invoices
      operationId: listInvoices
      security:
        - oauth2: [read]
        - apiKey: []
      responses:
        '200':
          description: A list of invoices
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Invoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      summary: Create an invoice
      operationId: createInvoice
      security:
        - oauth2: [write]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceCreate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '422':
          $ref: '#/components/responses/ValidationError'
components:
  securitySchemes:
    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/authorize
          tokenUrl: https://auth.example.com/token
          scopes:
            read: Read access to billing resources
            write: Write access to billing resources
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
  schemas:
    Invoice:
      type: object
      required: [id, amount_cents, currency, status]
      properties:
        id:
          type: string
        amount_cents:
          type: integer
          minimum: 0
        currency:
          type: string
          enum: [USD, EUR, GBP]
        status:
          type: string
          enum: [draft, open, paid, void]
        created_at:
          type: string
          format: date-time
    InvoiceCreate:
      type: object
      required: [amount_cents, currency]
      properties:
        amount_cents:
          type: integer
          minimum: 1
        currency:
          type: string
          enum: [USD, EUR, GBP]
    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
        request_id:
          type: string
  responses:
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: unauthorized
            message: API key is missing or invalid
            request_id: req_abc123
    Forbidden:
      description: Authenticated but not allowed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds before the next allowed request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationError:
      description: Request body failed validation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

The top-level security block sets the default for every operation; the per-operation security overrides it when an endpoint needs different scopes. Two equally valid auth options on the same endpoint are expressed as two array entries (OAuth2 OR API key); requiring both at once would be one entry with both keys. Error responses share a single Error schema so clients only have to learn one shape. The Retry-After header on the 429 response is the small detail that turns a working spec into one that is actually pleasant to integrate against.

OpenAPI specification example: webhook callbacks

Webhooks were a second-class citizen in OpenAPI 3.0. The 3.1 spec added a top-level webhooks key that is the right way to document them today. The example below shows both styles: the modern 3.1 webhooks block and the legacy callbacks pattern that still appears in older codebases. If you also need the long version of how to document webhook payloads, see how to document webhooks properly.

openapi: 3.1.0
info:
  title: Payments API
  version: 1.0.0
  description: Webhook OpenAPI specification example using the 3.1 webhooks key.
servers:
  - url: https://api.example.com/v1
paths:
  /subscriptions:
    post:
      summary: Subscribe to webhook events
      operationId: createSubscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                    enum: [payment.succeeded, payment.failed]
      responses:
        '201':
          description: Subscription created
webhooks:
  paymentSucceeded:
    post:
      summary: Sent when a payment succeeds
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentEvent'
      responses:
        '200':
          description: Acknowledged. Return 2xx within 5 seconds or we retry.
        '410':
          description: Endpoint gone. We will stop sending after 410.
  paymentFailed:
    post:
      summary: Sent when a payment fails
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentEvent'
      responses:
        '200':
          description: Acknowledged
components:
  schemas:
    PaymentEvent:
      type: object
      required: [id, type, created, data]
      properties:
        id:
          type: string
          example: evt_1abc
        type:
          type: string
          enum: [payment.succeeded, payment.failed]
        created:
          type: integer
          description: Unix timestamp
        data:
          type: object
          properties:
            payment_id:
              type: string
            amount_cents:
              type: integer
            currency:
              type: string

The webhooks block lives at the root, alongside paths. Each entry describes the request your server sends to the subscriber, plus the responses you expect back. Tools like Redoc and Stoplight render webhooks as their own section in the developer portal; older tools that only know 3.0 silently ignore the block, so for maximum compatibility, document webhook semantics in prose too.

YAML vs JSON for OpenAPI

Both formats are equally valid for OpenAPI specs. Pick one based on who is editing the file.

DimensionYAMLJSON
ReadabilityCleaner; no quoting noiseHeavier punctuation
Human authoringPreferredPainful at scale
Machine generationCommon but not standardDefault for code generators
CommentsSupportedNot supported
Tool supportUniversalUniversal
Common file nameopenapi.yamlopenapi.json

YAML wins for hand-written specs because of comments and lower visual noise. JSON wins for specs emitted by frameworks like FastAPI or NestJS, since those frameworks already serialize via JSON Schema. Some teams keep both: YAML in the repo for editing, a JSON build artifact for tooling that requires it. Either works.

Tools that use OpenAPI specs

A spec on its own is just a file. The tooling layer turns it into a developer portal, a client library, or a test suite. The most common renderers fall into four buckets.

  • Swagger UI: the original. Free, open source, ubiquitous. Renders any 3.x spec into an interactive reference with a "Try it out" panel.
  • Redoc: a more polished single-page renderer that powers the docs at GitHub, Docker, and many large APIs.
  • Stoplight: a hosted platform with a visual spec editor, mock server, and rendered docs. Good for teams that want to design the spec before writing code.
  • Mintlify and ReadMe: hosted docs platforms that consume an OpenAPI file and render a branded developer portal. Both start at $300+/mo, which is heavy for a small SaaS.
  • Docsio: feeds your OpenAPI spec or product URL into AI generation and ships a branded, hosted docs site in under five minutes. $60/mo per site for Pro features, free tier with one site, custom domain, and SSL.

For the wider category, the API documentation tool roundup compares the major options across price and setup time. If you only have an OpenAPI spec and want a working portal today, the AI generation flow handles ingestion, branding, and hosting in one pass.

Common mistakes in OpenAPI specs

These are the issues that show up over and over in spec reviews, in the order they cause the most pain.

  1. Missing operationId on operations. Code generators use operationId as the function name. Skip it and your generated SDK gets ugly auto-named methods like usersUserIdGetGet. Always set it.
  2. Inlining the same schema everywhere. A User object copy-pasted into ten responses is a nightmare to update. Define it once under components.schemas and $ref it.
  3. Forgetting the required array on object schemas. Without required, every property is optional, which silently breaks generated clients that expect non-null fields.
  4. Mixing 3.0 and 3.1 features. Tools that target 3.0 will fail on webhooks or JSON Schema 2020-12 keywords. Pick a version and stick to it.
  5. No servers block. Without it, Try it out panels in Swagger UI default to the docs site URL, which is almost never the API origin. Always declare at least one server.
  6. Vague descriptions. "User object" is not a description. State what the field means, what units it uses, and what edge cases exist. The spec is the developer's primary reference, not a side note.
  7. Hand-edited specs that drift from the code. Once the team is past 20 endpoints, the spec stops matching the implementation. Generate from code (FastAPI, NestJS, tsoa) or use a contract-first lint check in CI.

Fix those seven and the average OpenAPI spec quality jumps from "barely loads" to "I would actually integrate against this."

Frequently asked questions

What is a simple example of an OpenAPI specification?

The minimal valid OpenAPI specification example needs three keys at the root: openapi: 3.0.3, an info block with title and version, and either paths or components. A single GET endpoint that returns { message: "Hello, world" } is enough to load in Swagger UI and demonstrates every required structural piece.

Should I write OpenAPI in YAML or JSON?

YAML is preferred for hand-authored specs because it allows comments and reads more cleanly. JSON is preferred when the spec is generated by a framework like FastAPI or NestJS, since those tools already serialize via JSON Schema. Both formats are equally valid OpenAPI; tools accept either.

What is the difference between OpenAPI 3.0 and 3.1?

OpenAPI 3.1 aligns the schema dialect with JSON Schema 2020-12 and adds a top-level webhooks block. 3.0 is more widely supported by older tooling. For a new project, write 3.1 if your renderer supports it; otherwise 3.0.3 is the safe default with maximum compatibility.

Where can I find a working Petstore OpenAPI example?

The official Petstore spec lives at github.com/swagger-api/swagger-petstore and the OpenAPI Initiative hosts a curated set at learn.openapis.org. The CRUD example earlier in this article is a leaner version that demonstrates the same patterns in a smaller file.

How do I turn an OpenAPI spec into a documentation site?

Load the spec into a renderer. Swagger UI and Redoc are free and open source. Stoplight and Mintlify are hosted SaaS options. Docsio takes an OpenAPI spec or a product URL and generates a branded, hosted docs site automatically with one click of publish.

Putting an OpenAPI specification example to work

You now have four valid OpenAPI specification examples plus the patterns that matter most: shared schemas with $ref, structured error responses, OAuth2 plus API key, and webhook callbacks. The next step is rendering one of them. If you want a hosted developer portal without spending a weekend wiring up Docusaurus, point Docsio's AI generator at your spec or product URL and you have a branded site live in under five minutes. The free tier covers a single site with custom domain and SSL.

For the broader category and how OpenAPI fits into the rest of your docs stack, the API documentation best practices guide is the right next read.

Ready to ship your docs?

Generate a complete documentation site from your URL in under 5 minutes.

Get Started Free