Swagger JSON: Writing swagger.json Files, $ref Schemas & OpenAPI 3.1

Last updated:

A swagger.json file is an OpenAPI Specification document written in JSON format — it describes every endpoint, request parameter, response body, and authentication scheme for a REST API, and Swagger UI renders it as interactive documentation with zero additional code. A valid swagger.json requires exactly three top-level keys: openapi (version string, e.g. "3.1.0"), info (title and version), and paths (endpoint definitions). JSON format is functionally identical to YAML but stricter — all keys must be quoted strings, no comments are allowed, and trailing commas cause parse errors. Use {"$ref: "#/components/schemas/User""} to reference reusable schemas defined in components/schemas, eliminating duplication when the same object appears in multiple endpoints. OpenAPI 3.1 aligns with JSON Schema draft 2020-12, so type: ["string", "null"] replaces the OpenAPI 3.0 nullable: true pattern. This guide covers the complete swagger.json structure, defining request and response schemas, $ref references, authentication schemes, generating swagger.json automatically, and serving it with Swagger UI.

swagger.json Top-Level Structure: openapi, info, and paths

Every swagger.json document starts with three required top-level keys. openapi declares the specification version — use "3.1.0" for the latest version, or "3.0.3" for broader tooling compatibility. info contains API metadata including the required title and version strings, plus optional description, termsOfService, contact, and license fields. paths maps URL path strings to operation objects, where each HTTP method (get, post, put, delete, patch) defines one operation. The optional servers array specifies base URLs for different environments; without it, tools assume the document is served from its own URL.

{
  "openapi": "3.1.0",
  "info": {
    "title": "User Management API",
    "version": "1.0.0",
    "description": "REST API for managing users and their profiles",
    "contact": {
      "name": "API Support",
      "email": "api@example.com",
      "url": "https://example.com/support"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "https://api.example.com/v1",
      "description": "Production server"
    },
    {
      "url": "https://staging-api.example.com/v1",
      "description": "Staging server"
    },
    {
      "url": "http://localhost:3000/v1",
      "description": "Local development"
    }
  ],
  "paths": {
    "/users": {
      "get": {
        "operationId": "listUsers",
        "summary": "List all users",
        "description": "Returns a paginated list of all users in the system.",
        "tags": ["Users"],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of users",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserListResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createUser",
        "summary": "Create a new user",
        "tags": ["Users"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateUserRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created successfully",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/User" }
              }
            }
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ValidationError" }
              }
            }
          }
        }
      }
    }
  }
}

Each operation should have a unique operationId — a camelCase string that SDK generators use as the function name. The tags array groups operations in Swagger UI into collapsible sections. Always specify both success and error response codes; at minimum document 200/201 for success and 400/422/500 for errors. The servers array supports server variables for parameterized base URLs — useful for multi-tenant APIs where the subdomain varies per customer.

Defining Request Bodies and Response Schemas

In OpenAPI 3.x, request bodies are defined in the requestBody object (not as in: body parameters like in Swagger 2.0). The requestBody.content map supports multiple content types — most APIs use application/json, but you can also document multipart/form-data for file uploads or application/x-www-form-urlencoded. Response schemas are nested under responses[statusCode].content[contentType].schema. Use inline schemas for simple one-off shapes, and $ref for objects that appear in multiple places.

{
  "paths": {
    "/users/{userId}": {
      "get": {
        "operationId": "getUserById",
        "summary": "Get a user by ID",
        "tags": ["Users"],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" },
            "description": "The unique identifier of the user"
          }
        ],
        "responses": {
          "200": {
            "description": "User found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/User" },
                "example": {
                  "id": "550e8400-e29b-41d4-a716-446655440000",
                  "name": "Alice Smith",
                  "email": "alice@example.com",
                  "createdAt": "2026-01-15T10:30:00Z"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string", "example": "User not found" },
                    "code": { "type": "string", "example": "USER_NOT_FOUND" }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateUser",
        "summary": "Update a user",
        "tags": ["Users"],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to update — all fields are optional for PATCH",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UpdateUserRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "User updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/User" }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/avatar": {
      "post": {
        "operationId": "uploadAvatar",
        "summary": "Upload user avatar image",
        "tags": ["Users"],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Image file (JPEG or PNG, max 5 MB)"
                  }
                },
                "required": ["file"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Avatar uploaded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "avatarUrl": { "type": "string", "format": "uri" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Add example or examples fields to schemas and content objects — Swagger UI displays them in the request editor and response viewer, helping API consumers understand the expected data shape without reading the schema description. The format keyword provides semantic hints: uuid, date-time, uri, binary are common values. For binary file uploads, type: string, format: binary is the correct schema representation in OpenAPI 3.x.

Reusing Schemas with $ref and components/schemas

The components/schemas section is the schema library for your API document. Define any object that appears in more than one place — request bodies, response schemas, shared sub-objects — once under components/schemas, then reference it with {""$ref": "#/components/schemas/SchemaName""} everywhere it is needed. This eliminates copy-paste duplication and ensures that when you update a schema (adding a field, changing a type), all referencing locations are updated in one edit.

{
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "required": ["id", "name", "email", "createdAt"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "example": "Alice Smith"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "alice@example.com"
          },
          "role": {
            "type": "string",
            "enum": ["admin", "editor", "viewer"],
            "default": "viewer"
          },
          "profile": {
            "$ref": "#/components/schemas/UserProfile"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UserProfile": {
        "type": "object",
        "properties": {
          "bio": {
            "type": ["string", "null"],
            "maxLength": 500
          },
          "avatarUrl": {
            "type": ["string", "null"],
            "format": "uri"
          },
          "location": {
            "type": ["string", "null"],
            "example": "San Francisco, CA"
          }
        }
      },
      "CreateUserRequest": {
        "type": "object",
        "required": ["name", "email", "password"],
        "properties": {
          "name":     { "type": "string", "minLength": 1, "maxLength": 100 },
          "email":    { "type": "string", "format": "email" },
          "password": { "type": "string", "minLength": 8, "writeOnly": true },
          "role":     { "type": "string", "enum": ["admin", "editor", "viewer"] }
        }
      },
      "UpdateUserRequest": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "name":  { "type": "string", "minLength": 1, "maxLength": 100 },
          "email": { "type": "string", "format": "email" },
          "role":  { "type": "string", "enum": ["admin", "editor", "viewer"] }
        }
      },
      "UserListResponse": {
        "type": "object",
        "required": ["data", "pagination"],
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/User" }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["page", "limit", "total", "totalPages"],
        "properties": {
          "page":       { "type": "integer", "minimum": 1 },
          "limit":      { "type": "integer", "minimum": 1 },
          "total":      { "type": "integer", "minimum": 0 },
          "totalPages": { "type": "integer", "minimum": 0 }
        }
      },
      "ValidationError": {
        "type": "object",
        "required": ["success", "errors"],
        "properties": {
          "success": { "type": "boolean", "example": false },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": { "type": "string" }
            },
            "example": {
              "email": ["Invalid email address"],
              "name":  ["Name is required"]
            }
          }
        }
      }
    }
  }
}

$ref can also be used inside schemas to create composed types. Use allOf with $ref to extend a base schema: {"{ "allOf": [{ "$ref": "#/components/schemas/User" }, { "properties": { "adminNotes": { "type": "string" } } }] }"}. The writeOnly: true keyword on the password field tells Swagger UI not to display the field in response examples — the complementary readOnly: true prevents a field from appearing in request body editors.

Query Parameters, Path Parameters, and Headers

Parameters in OpenAPI 3.x have four possible locations set by the in field: path (required, interpolated into the URL), query (appended after ?), header (HTTP request header), and cookie (HTTP cookie). Path parameters must match a path template variable — /users/{'{userId}'} requires a parameter with name: "userId" and in: "path". Query parameters are optional by default; add required: true for mandatory filters. Reusable parameters can be defined under components/parameters and referenced with $ref.

{
  "components": {
    "parameters": {
      "UserIdParam": {
        "name": "userId",
        "in": "path",
        "required": true,
        "description": "Unique identifier of the user",
        "schema": { "type": "string", "format": "uuid" }
      },
      "PageParam": {
        "name": "page",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "default": 1 },
        "description": "Page number for pagination"
      },
      "LimitParam": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 },
        "description": "Number of results per page"
      },
      "AcceptLanguageHeader": {
        "name": "Accept-Language",
        "in": "header",
        "schema": { "type": "string", "example": "en-US,en;q=0.9" },
        "description": "Preferred language for error messages and content"
      }
    }
  },
  "paths": {
    "/users": {
      "get": {
        "operationId": "listUsers",
        "summary": "List users with filtering and sorting",
        "parameters": [
          { "$ref": "#/components/parameters/PageParam" },
          { "$ref": "#/components/parameters/LimitParam" },
          { "$ref": "#/components/parameters/AcceptLanguageHeader" },
          {
            "name": "role",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["admin", "editor", "viewer"]
            },
            "description": "Filter by user role"
          },
          {
            "name": "search",
            "in": "query",
            "schema": { "type": "string", "maxLength": 100 },
            "description": "Full-text search across name and email fields"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["name", "-name", "createdAt", "-createdAt"],
              "default": "-createdAt"
            },
            "description": "Sort field. Prefix with - for descending order."
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated user list",
            "headers": {
              "X-Total-Count": {
                "schema": { "type": "integer" },
                "description": "Total number of users matching the filter"
              },
              "X-Request-Id": {
                "schema": { "type": "string", "format": "uuid" },
                "description": "Unique identifier for this request, for debugging"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UserListResponse" }
              }
            }
          }
        }
      }
    },
    "/users/{userId}": {
      "parameters": [
        { "$ref": "#/components/parameters/UserIdParam" }
      ],
      "get": {
        "operationId": "getUserById",
        "summary": "Get a specific user",
        "responses": {
          "200": {
            "description": "User object",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/User" }
              }
            }
          }
        }
      }
    }
  }
}

Parameters defined at the path level (directly under the path key, not under an HTTP method) are inherited by all operations on that path — this is the correct pattern for path parameters like userId that apply to every verb on /users/{'{userId}'}. The headers field in a response documents response headers Swagger UI displays in the response details panel. Use style and explode fields to document complex parameter serialization formats — style: "form", explode: true is the default for query parameters, producing ?tags=a&tags=b for arrays.

Authentication: Bearer Tokens, API Keys, and OAuth 2.0

Security schemes are declared once in components/securitySchemes and applied to operations or globally via the security field. OpenAPI supports four scheme types: http (Bearer JWT, Basic auth), apiKey (header, query, or cookie), oauth2 (full OAuth 2.0 flows), and openIdConnect. A security requirement is an array of objects — multiple entries in the array mean OR (any scheme satisfies the requirement); multiple keys within a single object mean AND (all listed schemes must be provided). Swagger UI renders a padlock icon and an "Authorize" button for interactive credential entry.

{
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT token obtained from /auth/login. Include in Authorization header."
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key for server-to-server authentication"
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "session_token"
      },
      "oauth2": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://auth.example.com/oauth/authorize",
            "tokenUrl": "https://auth.example.com/oauth/token",
            "refreshUrl": "https://auth.example.com/oauth/refresh",
            "scopes": {
              "read:users":   "Read user profiles",
              "write:users":  "Create and update users",
              "delete:users": "Delete users",
              "admin":        "Full administrative access"
            }
          },
          "clientCredentials": {
            "tokenUrl": "https://auth.example.com/oauth/token",
            "scopes": {
              "read:users":  "Read user profiles",
              "write:users": "Create and update users"
            }
          }
        }
      }
    }
  },
  "security": [
    { "bearerAuth": [] }
  ],
  "paths": {
    "/users": {
      "get": {
        "operationId": "listUsers",
        "summary": "List users — requires read:users scope",
        "security": [
          { "bearerAuth": [] },
          { "oauth2": ["read:users"] },
          { "apiKeyAuth": [] }
        ],
        "responses": {
          "200": { "description": "User list" },
          "401": { "description": "Missing or invalid authentication" },
          "403": { "description": "Insufficient permissions" }
        }
      }
    },
    "/public/status": {
      "get": {
        "operationId": "getStatus",
        "summary": "Health check — no authentication required",
        "security": [],
        "responses": {
          "200": {
            "description": "API status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "example": "ok" },
                    "version": { "type": "string", "example": "1.0.0" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Setting security: [] on an individual operation overrides the global security requirement and marks that endpoint as public — this is the correct pattern for health checks, login endpoints, and public read-only endpoints. The global security field at the root level applies as a default to all operations that do not specify their own security. Always document 401 and 403 response codes on secured operations so API consumers understand the distinction between unauthenticated and unauthorized requests.

Generating swagger.json Automatically from Code

Hand-writing swagger.json for large APIs is error-prone and tedious — schemas drift out of sync with actual request/response types. Code-generation tools read your source code annotations and produce swagger.json automatically. swagger-jsdoc extracts @openapi JSDoc blocks from route files and merges them with a base configuration object. For TypeScript projects, tsoa generates swagger.json directly from TypeScript types and decorator metadata — no JSDoc needed.

{
  "openapi": "3.1.0",
  "info": {
    "title": "Generated API — swagger-jsdoc example output",
    "version": "1.0.0"
  },
  "paths": {
    "/users": {
      "get": {
        "operationId": "listUsers",
        "tags": ["Users"],
        "summary": "List all users",
        "description": "Generated from JSDoc @openapi annotation in routes/users.js",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": { "type": "integer", "default": 1 }
          }
        ],
        "responses": {
          "200": {
            "description": "List of users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/User" }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "description": "Generated from TypeScript interface User in models/user.ts",
        "required": ["id", "name", "email"],
        "properties": {
          "id":    { "type": "string", "format": "uuid" },
          "name":  { "type": "string" },
          "email": { "type": "string", "format": "email" }
        }
      }
    }
  }
}

For Express with swagger-jsdoc, call swaggerJsdoc({'{ definition: swaggerDefinition, apis: [\'./routes/**/*.js\'] }'}) to scan route files and merge all @openapi YAML blocks into one document. For NestJS, @nestjs/swagger reads @ApiProperty(), @ApiOperation(), and @ApiResponse() decorators and builds the document from the dependency-injected module graph — call SwaggerModule.createDocument(app, config) at startup. For Fastify, register @fastify/swagger and the JSON Schema you provide to route validation is automatically included in the swagger.json output, making request validation and documentation share a single schema definition.

OpenAPI 3.0 vs 3.1: Key JSON Format Differences

OpenAPI 3.1 (released March 2021) introduced full JSON Schema draft 2020-12 alignment, resolving years of divergence between the OpenAPI schema object and standard JSON Schema. The most visible JSON format change is the nullable keyword: in OpenAPI 3.0 you write nullable: true alongside a type; in OpenAPI 3.1 you use the standard JSON Schema array type type: ["string", "null"]. Several other keywords were updated or added to match JSON Schema behavior.

{
  "_comment_openapi_30_style": "OpenAPI 3.0 — nullable uses nullable keyword",
  "openapi_30_nullable_example": {
    "type": "object",
    "properties": {
      "bio": {
        "type": "string",
        "nullable": true,
        "description": "OpenAPI 3.0: nullable: true alongside type"
      },
      "age": {
        "type": "integer",
        "nullable": true,
        "minimum": 0
      }
    }
  },

  "_comment_openapi_31_style": "OpenAPI 3.1 — nullable uses JSON Schema array type",
  "openapi_31_nullable_example": {
    "type": "object",
    "properties": {
      "bio": {
        "type": ["string", "null"],
        "description": "OpenAPI 3.1: type array — 100% JSON Schema 2020-12 compatible"
      },
      "age": {
        "type": ["integer", "null"],
        "minimum": 0
      }
    }
  },

  "_comment_other_31_changes": "Other OpenAPI 3.1 additions",
  "openapi_31_new_features": {
    "type": "object",
    "properties": {
      "tags": {
        "type": "array",
        "items": { "type": "string" },
        "prefixItems": [
          { "type": "string", "description": "OpenAPI 3.1 supports prefixItems for tuple validation" }
        ],
        "unevaluatedItems": false
      },
      "config": {
        "if":   { "properties": { "env": { "const": "production" } } },
        "then": { "required": ["apiKey"] },
        "else": { "required": [] },
        "description": "OpenAPI 3.1: if/then/else is fully supported (not in 3.0)"
      }
    }
  },

  "_comment_exclusive_keywords": "exclusive keywords changed in 3.1",
  "openapi_30_exclusive": {
    "minimum": 0,
    "exclusiveMinimum": true,
    "description": "OpenAPI 3.0: exclusiveMinimum is a boolean flag alongside minimum"
  },
  "openapi_31_exclusive": {
    "exclusiveMinimum": 0,
    "description": "OpenAPI 3.1 / JSON Schema: exclusiveMinimum is a number, not a flag"
  },

  "_comment_webhooks": "OpenAPI 3.1 added webhooks as top-level field",
  "webhooks": {
    "userCreated": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/User" }
            }
          }
        },
        "responses": {
          "200": { "description": "Webhook received successfully" }
        }
      }
    }
  }
}

Before migrating from OpenAPI 3.0 to 3.1, audit your document for nullable: true occurrences and replace them with array types. The exclusiveMinimum and exclusiveMaximum keywords changed from boolean flags ({"exclusiveMinimum: true"} + minimum) to numeric values ({"exclusiveMinimum: 0"}), matching JSON Schema behavior. Tools like @redocly/openapi-cli migrate automate most of these transformations. Swagger Editor at editor.swagger.io supports both versions and highlights which keywords are invalid for the declared version.

FAQ

What is the difference between swagger.json and the OpenAPI Specification?

Swagger was the original name of the specification created by SmartBear. In 2016, Swagger 2.0 was donated to the OpenAPI Initiative and renamed the OpenAPI Specification (OAS). swagger.json is the JSON-format file that implements an OAS document — the term is still widely used because Swagger UI, Swagger Editor, and swagger-jsdoc kept their branding. OpenAPI 3.0 (released 2017) and OpenAPI 3.1 (released 2021) are the current versions; Swagger 2.0 is the legacy version. A swagger.json using "swagger": "2.0" is a Swagger 2.0 document; one using "openapi": "3.1.0" is an OpenAPI 3.1 document. The two formats are not directly compatible — OpenAPI 3.0 changed request bodies (replacing in: body parameters with requestBody), added the components object, and removed the host and basePath fields in favor of the servers array. OpenAPI 3.1 further aligns schemas with JSON Schema draft 2020-12, adding full compatibility with 100% of JSON Schema keywords.

What are the required fields in a swagger.json file?

A valid OpenAPI 3.x swagger.json requires exactly 3 top-level keys: "openapi" (a string like "3.1.0"), "info" (an object with "title" and "version" sub-fields), and "paths" (an object mapping URL paths to operation objects). The "info" object itself requires 2 fields: "title" (a string describing the API) and "version" (a string like "1.0.0"). "paths" can be an empty object {"{}"} if no endpoints are documented yet, producing a valid but useless document. Optional top-level keys include "servers" (base URLs), "components" (reusable schemas, parameters, responses), "security" (global auth requirements), "tags" (operation grouping), and "externalDocs". In Swagger 2.0, the required fields were "swagger": "2.0", "info", and "paths"; "host" and "basePath" were commonly added but optional. These location fields were removed entirely in OpenAPI 3.0 in favor of the "servers" array.

How do I use $ref to reference reusable schemas in swagger.json?

$ref is a JSON Reference — it replaces the object containing it with the value at the referenced location. The most common form is {""$ref": "#/components/schemas/SchemaName""}, where # means the root of the current document, components/schemas is the path, and SchemaName is the schema key. Define a schema once under components/schemas, then reference it wherever the schema appears — in request bodies, response schemas, array items, or as properties of other schemas. $ref can also reference other files: {""$ref": "./common-schemas.json#/components/schemas/Address""} loads from an external file. In the JSON Reference spec, no other keys may appear alongside a $ref in the same object — however, in OpenAPI 3.1 (aligned with JSON Schema draft 2020-12), $ref gained support for sibling keywords like "description" and "summary". Swagger UI and most tools resolve $ref references automatically when rendering documentation.

How do I document authentication in swagger.json?

Define authentication schemes in components/securitySchemes, then reference them with security requirements at the global or per-operation level. There are 4 scheme types: "http" (for Bearer tokens and Basic auth), "apiKey" (header, query, or cookie), "oauth2" (full OAuth 2.0 flows with 4 grant types), and "openIdConnect". For Bearer JWT: define {"{ "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }"} and add {""security": [{ "bearerAuth": [] }]"} to each operation. For API keys: define {"{ "type": "apiKey", "in": "header", "name": "X-API-Key" }"}. The security requirement array uses AND logic between objects and OR logic within a single object — {"[{ "bearerAuth": [], "apiKey": [] }]"} means both are required simultaneously, while {"[{ "bearerAuth": [] }, { "apiKey": [] }]"}means either satisfies the requirement. Swagger UI renders a padlock icon on each secured operation and provides an "Authorize" button to enter credentials for interactive testing.

How do I validate a swagger.json file?

Several tools validate swagger.json files against the OpenAPI Specification. The Swagger Editor at editor.swagger.io validates in real time as you type — paste or upload your swagger.json to see all errors and warnings immediately. The swagger-parser npm package provides programmatic validation: SwaggerParser.validate("./swagger.json") resolves $ref references and validates the full document, throwing a detailed error if anything is invalid. The openapi-validator CLI (npm install -g @ibm/openapi-validator) checks specification compliance and applies opinionated best-practice rules. For CI/CD pipelines, spectral (by Stoplight) validates with custom rulesets and integrates with GitHub Actions. Common validation errors include: missing required fields, invalid $ref paths that do not resolve, duplicate operationId values across paths, and response codes that are not valid 3-digit HTTP status codes. Swagger 2.0 documents are validated against a different JSON Schema than OAS 3.x — use a version-aware tool like swagger-parser that detects the version from the root key automatically.

How do I generate swagger.json automatically from my API code?

swagger-jsdoc generates swagger.json from JSDoc comments in your source code at build time. Annotate route handlers with @openapi JSDoc blocks, then run swagger-jsdoc to produce the complete swagger.json — zero manual JSON editing. For TypeScript projects, tsoa generates swagger.json from TypeScript decorators and type annotations, eliminating comment-based annotation entirely. For Express, express-swagger-generator and swagger-autogen scan route files automatically. For NestJS, @nestjs/swagger reads decorators like @ApiProperty() and @ApiOperation() to build the document. For Fastify, @fastify/swagger serializes route schema definitions to OpenAPI format. In all cases the generated swagger.json is committed to the repo or served dynamically at runtime from a /api-docs/swagger.json endpoint. The key advantage is that schemas stay synchronized with actual request/response types — when you change a TypeScript interface, the swagger.json updates on the next build, preventing the documentation drift that plagues hand-written specs.

What changed in OpenAPI 3.1 compared to Swagger 2.0 JSON format?

OpenAPI 3.1 (released March 2021) introduced 7 major changes from Swagger 2.0. First, the root version key changed from "swagger": "2.0" to "openapi": "3.1.0". Second, request bodies moved from parameters with "in": "body" to a dedicated "requestBody" object per operation. Third, host and basePath were replaced by the "servers" array, supporting multiple environments. Fourth, definitions was renamed to components/schemas, and all reusable objects moved into components. Fifth, OpenAPI 3.1 aligns 100% with JSON Schema draft 2020-12 — nullable: true is replaced by type: ["string", "null"], and all JSON Schema keywords are valid. Sixth, the "produces" and "consumes" fields were removed; content types are now specified per-operation in requestBody.content and responses.content. Seventh, 3.1 added support for webhooks as a top-level field. Migration tools like api-spec-converter and swagger-converter automate the Swagger 2.0 to OAS 3.x conversion.

How do I serve swagger.json with Swagger UI?

Swagger UI renders interactive API documentation from any valid swagger.json served at a URL — no backend changes needed beyond serving the file. The simplest approach: serve swagger.json as a static file and load Swagger UI from a CDN. Add an HTML page that references the swagger-ui-dist CDN bundle and sets url: "/swagger.json" in the SwaggerUIBundle config. For Express: install swagger-ui-express and swagger-jsdoc, then app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)). For Next.js: serve swagger.json from app/api/swagger.json/route.ts and add a /api-docs page that loads the swagger-ui-react component. For Docker: the official swaggerapi/swagger-ui image accepts a SWAGGER_JSON environment variable pointing to your file — 0 lines of code required. Swagger UI supports 3 layout modes (BaseLayout, StandaloneLayout, SidebarLayout), request interceptors for injecting authentication headers, and deep linking so specific operation URLs can be bookmarked and shared. The persistAuthorization: true option keeps entered credentials across page refreshes during development.

Format and validate swagger.json online

Paste your swagger.json into Jsonic's JSON formatter to check syntax, validate structure, and pretty-print the document in seconds.

Open JSON Formatter

Further reading and primary sources