OpenAPI and Swagger for systems analysts

Train for your next tech interview
1,500+ real interview questions across engineering, product, design, and data — with worked solutions.
Join the waitlist

Why interviewers ask about OpenAPI

If you sit a systems analyst loop at Stripe, Linear, Airbnb, or any payments-adjacent shop, you will be asked to describe a new endpoint in OpenAPI 3.1. The interviewer is checking three things at once: do you know the format, do you know what makes a contract testable, and can you reason about backward compatibility without losing clients. The whole loop usually fits in 30 minutes and you are expected to write YAML on the whiteboard, not just talk about it.

The pain the question protects against is real. A systems analyst who writes "the endpoint accepts a JSON with user_id" in a Notion doc forces the backend engineer and the integration team to re-derive the schema, ship parallel JSON files, and discover a mismatch on the day before launch. A proper OpenAPI document collapses that into one file that lints, generates clients, and survives review.

Load-bearing trick: treat the OpenAPI file as the contract, not the docs. Everything else — Swagger UI, Postman collections, codegen — is a downstream artifact of the same source of truth.

OpenAPI vs Swagger terminology

The OpenAPI Specification (OAS) is the formal grammar for describing REST APIs. Current version is 3.1.0, released in 2021 and aligned with JSON Schema 2020-12.

Swagger is the historical name (everything up to 2.0). The project was renamed to OpenAPI in 2016 when SmartBear donated it to the Linux Foundation, but the Swagger brand stuck to the tooling:

Tool What it does
Swagger UI Renders the spec as interactive HTML docs
Swagger Codegen Generates client SDKs and server stubs
Swagger Editor Browser-based YAML editor with live preview
Redoc Alternative renderer, three-pane layout
Spectral Linter, custom rulesets, CI-friendly

The correct phrasing in 2026 is "described in OpenAPI" but "served via Swagger UI" — both are accepted.

3.1 vs 3.0 is the second half of this question. Be ready to name three differences: full JSON Schema 2020-12 compatibility (so if/then/else, $dynamicRef, unevaluatedProperties work), a new top-level webhooks block, and the removal of nullable: true in favor of type: [string, "null"]. Most interviewers do not care about 3.0 specifics anymore, but the migration story matters because half of all production APIs still live on 3.0.

Spec structure

An OpenAPI document is YAML or JSON. The root keys are stable across the 3.x line:

openapi: 3.1.0

info:
  title: Orders API
  version: 1.2.0
  description: API for managing orders, payments, and shipments.
  contact:
    name: Platform team
    email: platform@example.com

servers:
  - url: https://api.example.com/v1
    description: Production
  - url: https://staging.api.example.com/v1
    description: Staging

tags:
  - name: orders
    description: Order lifecycle

paths:
  /orders: { }
  /orders/{id}: { }

components:
  schemas: { }
  parameters: { }
  responses: { }
  securitySchemes: { }

security:
  - bearerAuth: []

The top-level security block sets the default for every operation. Operation-level security overrides it, including the empty array for public endpoints.

Paths and operations

Each path entry is an object whose keys are HTTP methods: get, post, put, patch, delete, head, options. An operation must declare its parameters, request body (where relevant), and one entry per response status code you actually return.

paths:
  /orders/{id}:
    get:
      summary: Get an order by ID
      operationId: getOrder
      tags: [orders]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          description: Order not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

The parameters.in field takes four values: path for variables inside the URL template, query for ?status=paid, header for request headers, and cookie for cookies. Path parameters are always required.

For POST and PUT you describe the body with requestBody:

post:
  summary: Create an order
  operationId: createOrder
  requestBody:
    required: true
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/CreateOrderRequest'
  responses:
    '201':
      description: Created
      headers:
        Location:
          schema: { type: string }
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Order'

Every operation must have a unique operationId in kebab-case or camelCase. Codegen turns it into the method name on the generated client (client.getOrder(id)). If you skip it, Swagger Codegen falls back to path + method and you end up with ordersIdGet — readable to nobody.

Components: schemas, parameters, responses

The components section holds reusable building blocks. Anything referenced more than once should live here and be pulled in with $ref.

components:
  schemas:
    Order:
      type: object
      required: [id, status, amount]
      properties:
        id:
          type: integer
          format: int64
        status:
          type: string
          enum: [pending, paid, shipped, cancelled]
        amount:
          type: number
          format: decimal
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
        created_at:
          type: string
          format: date-time

    Error:
      type: object
      required: [code, message]
      properties:
        code: { type: string }
        message: { type: string }
        details: { type: object }

  parameters:
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20

  responses:
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

Reuse looks like this:

paths:
  /orders:
    get:
      parameters:
        - $ref: '#/components/parameters/LimitParam'
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'

Useful built-in formats: int32 and int64 for integers, date and date-time for timestamps, uuid, email, uri for strings, and binary or byte (base64) for file payloads. The password hint tells Swagger UI to render the input masked.

Train for your next tech interview
1,500+ real interview questions across engineering, product, design, and data — with worked solutions.
Join the waitlist

Security: bearer, apiKey, oauth2

Security schemes are declared once in components.securitySchemes and then referenced from security. The three flavors you will be asked about are bearer tokens, API keys, and OAuth2.

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://example.com/oauth/authorize
          tokenUrl: https://example.com/oauth/token
          scopes:
            read:orders: Read orders
            write:orders: Create and modify orders

security:
  - bearerAuth: []

An individual operation can opt out of the default — useful for health checks and public webhooks:

paths:
  /public/health:
    get:
      security: []

Or it can require specific OAuth2 scopes:

post:
  security:
    - oauth2: [write:orders]

Versioning and evolution

Three places to put the version, in decreasing order of popularity: in the URL (/v1/orders, the dominant pattern at Stripe, GitHub, and most B2B APIs), in a header (Accept: application/vnd.api.v1+json, common at Twilio and GitHub's GraphQL surface), and in a query parameter (/orders?version=1, generally a sign somebody lost a fight with infra).

The info.version field in the spec is the document version, not the API version — it bumps every time you publish a schema change, including non-breaking ones. Treat it as semver of the YAML file.

Change Type Action
Add optional query param Backward-compatible Bump minor
Add new field to response Backward-compatible Bump minor
Add new endpoint Backward-compatible Bump minor
Make optional field required Breaking Bump major, parallel deploy
Remove field or enum value Breaking Bump major, deprecation window
Change a field's type Breaking Bump major, parallel deploy

For breaking changes, the standard playbook is parallel deploy: ship v2 alongside v1, mark v1 operations with deprecated: true, and run a deprecation window of 6 to 12 months before turning the old paths off. Swagger UI renders deprecated operations struck through, which is usually enough nudge for client teams.

get:
  deprecated: true
  description: Use /v2/orders/{id} instead. v1 will be removed on 2027-01-01.

Common pitfalls

The single most common interview-day mistake is not using $ref. Candidates inline the same Order schema in five operations, then explain how they would "remember to update all of them" when the field set changes. Pull every reusable object into components.schemas from the start; the linter will not catch this for you and code review usually misses it too.

The second is forgetting required. JSON Schema treats every property as optional by default, so a schema that lists ten fields without a required array is documenting that the server might return any subset. Production fallout is real: backend nulls become client undefined, and a serialization bug masquerades as a business logic issue for a week.

A close third is skipping error schemas. The document describes the happy path with a single 200 response, and the integration team has to grep server code to learn that validation failures return 422 with a details array, while rate limiting returns 429 with a Retry-After header. Every operation should list every status code it actually returns, and the error body should be a reusable schema.

Generic typing is the elegant version of the same mistake — declaring payload: object because you do not want to commit to a structure. Codegen produces Map<String, Object> clients, Swagger UI shows an empty box, and consumers send whatever they want. If the payload is genuinely polymorphic, use oneOf with a discriminator; if it is opaque (an analytics blob, a third-party callback), at least add an additionalProperties schema and an example.

URL-only versioning without a deprecation policy is the way v1 lives forever. Clients keep pointing at it, and the team that owns v2 cannot delete the old code because nobody knows who still depends on it. Pair every URL version with a published end-of-life date and a metric on the User-Agent header so you can see who is still on the old version.

Finally, confidential data in description keeps catching new SAs. The spec ships to Swagger UI on a public docs site, and lines like "use the test key XXX-YYY" end up indexed by Google within a week. Treat the spec like any other published artifact — secrets do not belong in it.

If you want to drill SA contract questions like this every day, NAILDD is launching with hundreds of systems analyst problems built around exactly this pattern.

FAQ

Do I write the spec by hand or generate it from code?

Two camps. Design-first means you author the OpenAPI YAML before any code, review it with backend and frontend together, and only then implement. Code-first generates the spec from annotations on controllers (Spring's springdoc, FastAPI, NestJS). Design-first scales better in teams with a real systems analyst because the contract review happens upstream of implementation; code-first is fine for backend-only teams with no SA in the loop. In interviews, expect design-first — the question is usually "open a YAML file and describe this endpoint", not "annotate this Java method".

How do I test an API against its OpenAPI spec?

The standard tools are Dredd and Schemathesis, both of which read the spec and generate requests to validate that the server actually conforms. Schemathesis adds property-based fuzzing on top, which catches edge cases like empty arrays and oversized strings that hand-written tests usually miss. For collection-style testing, Postman imports OpenAPI documents and turns each operation into a request; pair it with Newman in CI for smoke tests on every deploy.

Does OpenAPI describe GraphQL or gRPC?

No. OpenAPI is REST-only. GraphQL uses SDL (Schema Definition Language) introspected from the server. gRPC uses Protocol Buffers (.proto files) and the schema is the source of truth for both client and server generation. A reasonable cross-protocol comparison is part of most senior SA loops, so be ready to sketch the differences and explain which problem each format solves best.

What is discriminator in OAS?

A discriminator is the field whose value tells the parser which schema in a oneOf or anyOf is actually being used. Without it, the parser has to try each schema in turn and pick the first that validates — slow and ambiguous. With it, the parser reads the discriminator field and jumps directly to the correct schema.

Pet:
  oneOf:
    - $ref: '#/components/schemas/Cat'
    - $ref: '#/components/schemas/Dog'
  discriminator:
    propertyName: pet_type
    mapping:
      cat: '#/components/schemas/Cat'
      dog: '#/components/schemas/Dog'

Can OpenAPI describe webhooks?

In 3.1 yes, through the top-level webhooks block — it mirrors the paths structure but flips the direction, declaring that the server will call out to a URL the client owns. In 3.0 the closest equivalent is callbacks nested inside a specific operation, which is awkward when the webhook is unrelated to any single API call. If your team is on 3.0 and ships webhooks, the migration to 3.1 is usually worth a quarter of cleanup time.

Is everything in this article authoritative?

No. The article is based on the OpenAPI 3.1 specification (spec.openapis.org/oas/v3.1.0) and Swagger documentation. Tooling behavior changes; always check the version notes for Spectral, Schemathesis, and your codegen of choice before relying on a specific feature in production.