DEV_NET_CORE
GET_STARTED
Design & ArchitectureAPI design and integration contracts

OpenAPI contracts and consumer-facing documentation

Overview

OpenAPI is a language-independent specification for describing HTTP APIs in a machine-readable JSON or YAML document. It describes operations, paths, parameters, request bodies, responses, schemas, authentication mechanisms, examples, callbacks, webhooks, and reusable components.

An OpenAPI document can support:

  • Interactive reference documentation.
  • Client and server code generation.
  • Contract testing.
  • Request and response validation.
  • API linting and governance.
  • Mock servers.
  • Compatibility analysis.
  • Gateway and developer-portal configuration.

OpenAPI is not a substitute for all documentation. A consumer also needs task-oriented guides, authentication instructions, workflow explanations, error and retry behavior, rate limits, versioning policy, examples, and support information.

A strong API contract answers both structural and behavioral questions:

  • Which request is valid?
  • Which responses can occur?
  • Which fields are required or nullable?
  • How is authentication supplied?
  • Is an operation idempotent?
  • How should pagination and retries work?
  • Which errors are stable enough for automation?
  • How does a consumer complete a real business workflow?

This topic matters in interviews because candidates must connect design, implementation, testing, documentation, compatibility, and consumer experience. Producing a generated Swagger page is not enough if the document is incomplete, inaccurate, or unusable for client generation.

Core Concepts

OpenAPI Description Versus API Version

The OpenAPI document contains two different version concepts:

Code
openapi: 3.1.0
info:
  title: Ordering API
  version: 2.3.0
  • openapi identifies the OpenAPI Specification feature set used by the document.
  • info.version identifies the described API contract or document version.

Neither automatically defines the runtime versioning strategy. An API can use path, header, query, or media-type versioning independently.

Use a specification version supported by the team's tooling. The current OpenAPI Specification continues to evolve, while generators and frameworks may support earlier versions such as OpenAPI 3.1 or 3.0.

Main OpenAPI Structure

A simplified document:

Code
openapi: 3.1.0
info:
  title: Ordering API
  version: 1.0.0
  description: Creates and manages customer orders.
servers:
  - url: https://api.example.com
paths:
  /orders/{orderId}:
    get:
      operationId: getOrder
      summary: Retrieve an order
      parameters:
        - $ref: '#/components/parameters/OrderId'
      responses:
        '200':
          description: The order
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  schemas:
    Order:
      type: object
      required:
        - id
        - status
      properties:
        id:
          type: string
          example: ord_123
        status:
          type: string
          enum:
            - draft
            - submitted
            - paid

The contract should be valid for tooling and understandable to a human reviewer.

Paths and Operations

Each operation should define:

  • HTTP method and path.
  • Stable operationId.
  • Summary and detailed description.
  • Tags.
  • Parameters.
  • Request-body media types and schema.
  • Every expected response.
  • Security requirements.
  • Deprecation state.

Example:

Code
post:
  operationId: createOrder
  summary: Create a draft order
  tags:
    - Orders
  requestBody:
    required: true
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/CreateOrderRequest'
  responses:
    '201':
      description: Order created
      headers:
        Location:
          description: URI of the created order
          schema:
            type: string
            format: uri-reference
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Order'
    '422':
      $ref: '#/components/responses/ValidationProblem'

Documenting only a successful 200 response hides important contract behavior.

Stable Operation IDs

operationId uniquely identifies an operation and is frequently used as a generated client method name.

Prefer:

Code
getOrder
createOrder
cancelOrder
listCustomerOrders

Avoid unstable values derived from implementation method names or route text.

Changing an operation ID can break generated clients even if the HTTP route remains compatible. Treat it as consumer-facing contract.

Parameters

OpenAPI distinguishes:

  • Path parameters.
  • Query parameters.
  • Header parameters.
  • Cookie parameters.

Document:

  • Data type and format.
  • Whether required.
  • Default and allowed values.
  • Serialization style.
  • Description and example.
  • Constraints such as minimum and maximum.
Code
- name: limit
  in: query
  required: false
  description: Maximum number of orders to return.
  schema:
    type: integer
    minimum: 1
    maximum: 100
    default: 50

Every path-template parameter must be defined and required.

Request Bodies and Media Types

Specify the accepted media types and schema:

Code
requestBody:
  required: true
  content:
    application/json:
      schema:
        $ref: '#/components/schemas/CreateOrderRequest'

An operation can support several media types:

Code
content:
  application/json:
    schema:
      $ref: '#/components/schemas/ImportRequest'
  text/csv:
    schema:
      type: string

Do not document application/json if the implementation requires a vendor-specific or patch media type.

Schema Precision

Schemas should express what consumers can rely on:

Code
Money:
  type: object
  required:
    - amount
    - currency
  properties:
    amount:
      type: number
      multipleOf: 0.01
      example: 125.50
    currency:
      type: string
      pattern: '^[A-Z]{3}$'
      example: USD

Useful constraints include:

  • required.
  • minimum and maximum.
  • minLength and maxLength.
  • pattern.
  • format.
  • enum.
  • minItems and maxItems.
  • uniqueItems.
  • Composition such as oneOf, anyOf, and allOf.

Do not add constraints that the runtime does not enforce. Do not omit constraints that clients must satisfy.

Required, Optional, and Nullable

These are distinct:

  • Required means the property must appear.
  • Optional means it can be omitted.
  • Nullable means null is an allowed value.

In an OpenAPI 3.1-style schema:

Code
middleName:
  type:
    - string
    - 'null'

An optional nullable field can be absent or explicitly null. A required nullable field must be present but may be null.

This distinction affects validation and generated types.

Read-Only and Write-Only Properties

Use schema annotations to clarify direction:

Code
id:
  type: string
  readOnly: true

password:
  type: string
  format: password
  writeOnly: true

These annotations do not implement authorization or data masking. The server must still enforce behavior.

Separate request and response schemas when one shared schema becomes confusing.

Enums and Compatibility

Enums improve discoverability but can create compatibility risk.

Code
status:
  type: string
  enum:
    - pending
    - completed
    - failed

Adding a value can break generated clients that model a closed enum.

Options include:

  • Document that consumers must tolerate unknown values.
  • Generate extensible-enum types where supported.
  • Use a string plus documented known values.
  • Introduce a version for an incompatible semantic change.

Never reuse an existing enum value with a new meaning.

Polymorphism

OpenAPI supports schema composition and discriminators:

Code
PaymentMethod:
  oneOf:
    - $ref: '#/components/schemas/CardPaymentMethod'
    - $ref: '#/components/schemas/BankTransferMethod'
  discriminator:
    propertyName: type
    mapping:
      card: '#/components/schemas/CardPaymentMethod'
      bankTransfer: '#/components/schemas/BankTransferMethod'

Use polymorphism only when it reflects a stable contract. Complex inheritance models can generate poor client code across languages.

Prefer a clear discriminator and validate every supported variant.

Reusable Components

components can hold reusable:

  • Schemas.
  • Parameters.
  • Headers.
  • Responses.
  • Examples.
  • Security schemes.
  • Request bodies.
  • Links.
  • Callbacks.
Code
components:
  responses:
    NotFound:
      description: The resource was not found.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'

Reuse reduces duplication, but excessive indirection makes documents hard to read. Share concepts that are genuinely the same contract.

Examples

Examples make abstract schemas concrete:

Code
examples:
  paidOrder:
    summary: A paid order ready for fulfillment
    value:
      id: ord_123
      status: paid
      total:
        amount: 125.50
        currency: USD

Provide examples for:

  • Typical success.
  • Validation errors.
  • Authentication and authorization failures.
  • Conflict states.
  • Pagination.
  • Asynchronous operations.
  • Edge cases.

Validate examples against schemas in CI. Stale examples damage trust.

Response Documentation

Document all deliberate responses:

Code
responses:
  '200':
    description: Order returned
  '304':
    description: Cached representation remains current
  '401':
    $ref: '#/components/responses/Unauthorized'
  '403':
    $ref: '#/components/responses/Forbidden'
  '404':
    $ref: '#/components/responses/NotFound'
  '429':
    $ref: '#/components/responses/RateLimited'

Include relevant headers:

  • Location.
  • ETag.
  • Retry-After.
  • Pagination links.
  • Rate-limit metadata.
  • Deprecation and sunset information.

A default response can describe unexpected errors but should not replace known status-specific contracts.

Problem Details

Use a reusable Problem Details schema:

Code
ProblemDetails:
  type: object
  properties:
    type:
      type: string
      format: uri-reference
    title:
      type: string
    status:
      type: integer
    detail:
      type: string
    instance:
      type: string
      format: uri-reference
    traceId:
      type: string

Document stable extension members such as:

  • Machine-readable error code.
  • Field errors.
  • Correlation ID.
  • Retryability.

Consumers should not parse human-readable detail text.

Security Schemes

OpenAPI can describe authentication mechanisms:

Code
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
Code
security:
  - bearerAuth: []

OAuth scopes:

Code
security:
  - oauth2:
      - orders.read
      - orders.write

Security declarations describe how credentials are supplied. They do not express every authorization rule, such as resource ownership or tenant isolation.

Never place real secrets or usable production tokens in examples.

Callbacks and Webhooks

OpenAPI can describe requests sent by the provider to a consumer.

Document:

  • Registration.
  • Event types.
  • Payload schemas.
  • Signature verification.
  • Retry policy.
  • Delivery ordering.
  • Duplicate handling.
  • Expected responses.
  • Timeout behavior.

A schema alone is not enough for reliable webhook integration.

Links can describe a relationship from one response to another operation:

Code
links:
  GetCreatedOrder:
    operationId: getOrder
    parameters:
      orderId: '$response.body#/id'

Links improve workflow discoverability and can support generated clients or documentation tools, though tooling support varies.

Contract-First Development

Contract-first workflow:

Code
Design OpenAPI contract
  -> review with consumers
  -> lint and validate
  -> generate mocks or stubs
  -> implement provider and clients
  -> verify implementation

Benefits:

  • Consumers can review before implementation.
  • Parallel frontend and backend development.
  • Earlier compatibility and governance checks.
  • Contract drives implementation intentionally.

Costs:

  • Requires disciplined synchronization.
  • Generated server code can constrain implementation.
  • Teams need specification expertise.

Code-First Development

Code-first workflow:

Code
Implement annotated endpoints
  -> generate OpenAPI document
  -> publish and test contract

Benefits:

  • Implementation and contract metadata remain close.
  • Productive for existing ASP.NET Core APIs.
  • Type information can be inferred.

Risks:

  • Generated descriptions can be incomplete.
  • Implementation names can leak into the contract.
  • Error responses, examples, and behavior require deliberate metadata.
  • Contract review may happen too late.

Code-first does not mean contract-last. Generate and review the document in CI before release.

Hybrid Workflow

A practical hybrid:

  1. Design important paths and schemas with consumers.
  2. Implement using typed endpoint metadata.
  3. Generate the OpenAPI document.
  4. Diff it against the approved contract.
  5. Run contract and example tests.
  6. Publish the generated artifact.

The source of truth must be explicit. Two independently edited documents will drift.

ASP.NET Core OpenAPI Generation

Modern ASP.NET Core provides built-in OpenAPI document generation through Microsoft.AspNetCore.OpenApi.

Code
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.Run();

The generated document is available from a route such as:

Code
/openapi/v1.json

ASP.NET Core can generate multiple documents for audiences or versions and can generate documents at build time. Interactive UIs are separate tools and are not the OpenAPI document itself.

Endpoint Metadata in ASP.NET Core

Code
app.MapGet(
    "/orders/{orderId}",
    async Task<Results<Ok<OrderResponse>, NotFound>> (
        string orderId,
        IOrderQueries queries,
        CancellationToken cancellationToken) =>
    {
        var order = await queries.GetAsync(
            orderId,
            cancellationToken);

        return order is null
            ? TypedResults.NotFound()
            : TypedResults.Ok(order);
    })
    .WithName("GetOrder")
    .WithSummary("Retrieve an order")
    .WithDescription(
        "Returns an order visible to the authenticated caller.")
    .WithTags("Orders")
    .Produces<OrderResponse>(StatusCodes.Status200OK)
    .Produces(StatusCodes.Status404NotFound);

Stable endpoint names commonly become operation IDs. Typed results and response metadata help generate accurate response contracts.

Document Transformers

Generation pipelines can add:

  • API metadata.
  • Security schemes.
  • Common headers.
  • Environment-specific servers.
  • Standard error responses.
  • Naming conventions.

Transformers should not hide missing endpoint design. Keep custom logic deterministic and test generated output.

Build-Time Generation

Build-time generation is useful when the document is:

  • Stored as a release artifact.
  • Diffed for breaking changes.
  • Used to generate clients.
  • Published statically.
  • Used by contract tests.

The generation process can execute application startup. Avoid requiring unavailable databases, secrets, or external services merely to produce the contract.

Contract Validation

CI should validate:

  • Document syntax.
  • References.
  • Unique operation IDs.
  • Required metadata.
  • Schema consistency.
  • Examples against schemas.
  • Organization naming rules.
  • Security declarations.
  • Known response coverage.

Lint rules should support consumer value rather than enforce arbitrary style.

Breaking-Change Detection

Compare the proposed document with the released baseline.

Potential breaking changes include:

  • Removed operation.
  • Removed response or media type.
  • New required request field.
  • Narrowed accepted values.
  • Changed type or format.
  • Removed enum value.
  • Changed operation ID.
  • Changed authentication requirement.
  • Changed parameter location.

Automated diffing catches structural changes. Human review is still needed for semantic changes that keep the same schema.

Generated Clients

Generated clients can provide:

  • Typed request and response models.
  • Serialization.
  • Route construction.
  • Authentication hooks.
  • Basic error handling.

Risks include:

  • Poor names from unstable operation IDs.
  • Closed enums.
  • Large generated code changes.
  • Generator-specific behavior.
  • Runtime and dependency coupling.

Pin generator versions, inspect output, and wrap generated clients when application code needs insulation.

Contract Testing

Useful tests include:

  • Provider implementation matches the OpenAPI response schema.
  • Consumer requests conform to the contract.
  • Examples remain valid.
  • Important workflows work against a mock and real provider.
  • Older released contracts remain supported.
  • Generated clients compile and pass smoke tests.

Schema validation cannot prove business semantics, authorization, or idempotency. Combine it with behavioral tests.

Consumer-Facing Documentation

A useful documentation portal includes:

  • Overview and intended audience.
  • Getting started.
  • Base URLs and environments.
  • Authentication setup.
  • Quick-start request and response.
  • Task-oriented guides.
  • Endpoint reference.
  • Errors and troubleshooting.
  • Pagination, filtering, and sorting.
  • Idempotency and retry guidance.
  • Rate limits.
  • Webhook behavior.
  • Versioning, changelog, deprecation, and migration.
  • SDK instructions.
  • Support and service expectations.

Reference documentation answers "what exists." Guides answer "how do I accomplish my goal."

Examples and Tutorials

Examples should be:

  • Runnable.
  • Minimal but realistic.
  • Free of secrets.
  • Versioned with the API.
  • Tested automatically.
  • Available in common client languages where justified.

Show complete workflows:

Code
Authenticate
  -> create order with idempotency key
  -> retrieve order
  -> handle validation conflict
  -> paginate order history

Isolated curl snippets are helpful but do not replace workflow guidance.

Documentation for Behavior OpenAPI Cannot Fully Express

Document explicitly:

  • Idempotency-key retention.
  • Eventual consistency.
  • Retryable status codes.
  • Rate-limit windows.
  • Ordering guarantees.
  • Pagination snapshot behavior.
  • Webhook redelivery.
  • Data freshness.
  • Authorization ownership rules.
  • Long-running operation lifecycle.
  • Support and uptime commitments.

Use OpenAPI extensions sparingly when tooling consumes them. Otherwise keep behavioral documentation beside the reference.

Public and Internal Documents

A service can produce different documents for:

  • Public consumers.
  • Partner consumers.
  • Internal operators.
  • API versions.

Do not rely on documentation filtering as access control. Undocumented endpoints still require authentication and authorization.

Avoid publishing internal topology, administrative operations, or sensitive schemas unnecessarily.

Documentation Ownership

The team that owns the API should own its contract and documentation.

Include documentation in the definition of done:

  • Contract updated.
  • Examples updated.
  • Compatibility reviewed.
  • Changelog written.
  • Migration notes supplied for breaking changes.
  • Consumer feedback collected.

Documentation that is updated by a separate team after release will predictably drift.

Common Mistakes

  • Treating Swagger UI as complete consumer documentation.
  • Generating a contract without reviewing it.
  • Documenting only success responses.
  • Unstable or duplicate operation IDs.
  • Returning schemas that differ from runtime JSON.
  • Confusing OpenAPI version with API version.
  • Omitting nullability and required-property semantics.
  • Publishing ORM or domain entities directly.
  • Adding examples that fail schema validation.
  • Declaring authentication without explaining authorization.
  • Assuming generated clients tolerate every additive change.
  • Editing code and a separate contract independently.
  • Skipping compatibility diffing.
  • Exposing internal endpoints through a public document.
  • Describing behavior the server does not enforce.

Best Practices

  • Treat the OpenAPI description as a release artifact and consumer contract.
  • Choose one explicit source of truth.
  • Use stable operation IDs and business-facing schema names.
  • Document all expected responses, headers, and media types.
  • Express required, nullable, read-only, and write-only semantics precisely.
  • Provide validated success and error examples.
  • Describe authentication and stable authorization requirements.
  • Generate and lint the document in CI.
  • Diff against the last released contract.
  • Test provider behavior and generated clients.
  • Pair reference documentation with task-oriented guides.
  • Document behavior that schemas cannot capture.
  • Publish changelogs, deprecation notices, and migration guides.
  • Keep public and internal contracts intentionally separated.

Interview Practice

PreviousAPI gateway and BFF decisions for web clients and microservicesNext UpResource modeling, REST semantics, and when RPC-style endpoints are acceptable