Swagger paths define the endpoints of your API, and operations specify the HTTP methods and details for interacting with those endpoints.
Let’s look at an actual Swagger definition and see these concepts in action. Imagine we have a simple API for managing a list of "widgets."
openapi: 3.0.0
info:
title: Widget API
version: 1.0.0
paths:
/widgets:
get:
summary: List all widgets
responses:
'200':
description: A list of widgets.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Widget'
post:
summary: Create a new widget
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewWidget'
responses:
'201':
description: Widget created successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/Widget'
/widgets/{widgetId}:
get:
summary: Get a specific widget by ID
parameters:
- name: widgetId
in: path
required: true
schema:
type: string
description: The ID of the widget to retrieve.
responses:
'200':
description: The requested widget.
content:
application/json:
schema:
$ref: '#/components/schemas/Widget'
put:
summary: Update an existing widget
parameters:
- name: widgetId
in: path
required: true
schema:
type: string
description: The ID of the widget to update.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateWidget'
responses:
'200':
description: Widget updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/Widget'
delete:
summary: Delete a widget
parameters:
- name: widgetId
in: path
required: true
schema:
type: string
description: The ID of the widget to delete.
responses:
'204':
description: Widget deleted successfully.
components:
schemas:
Widget:
type: object
properties:
id:
type: string
name:
type: string
description:
type: string
NewWidget:
type: object
properties:
name:
type: string
description:
type: string
UpdateWidget:
type: object
properties:
name:
type: string
description:
type: string
In this example, the paths object is the core. It’s a map where each key is a URL path.
/widgetsis a path./widgets/{widgetId}is another path. The{widgetId}part signifies a path parameter, a dynamic segment that will be replaced by an actual ID when a request is made.
Underneath each path, you define the operations that can be performed on that path. These are the standard HTTP methods: get, post, put, delete, patch, options, head, and trace.
For the /widgets path:
- The
getoperation describes how to retrieve a list of all widgets. It specifies a200response with a JSON array ofWidgetobjects. - The
postoperation details how to create a new widget. It requires arequestBodycontaining aNewWidgetobject and returns a201status code with the newly createdWidget.
For the /widgets/{widgetId} path:
- The
getoperation retrieves a single widget. Notice theparameterssection. It explicitly defineswidgetIdas apathparameter, meaning it’s expected to be part of the URL itself (e.g.,/widgets/abc-123). - The
putoperation allows updating an existing widget, again using thewidgetIdpath parameter and expecting anUpdateWidgetobject in the request body. - The
deleteoperation removes a widget, identified by itswidgetId.
This structure provides a clear contract for how clients should interact with your API. The summary and description fields are crucial for human readability and documentation generation. The responses object outlines all possible outcomes of an operation, including status codes, descriptions, and the structure of the response body (content and schema).
The most surprising thing is how much of your API’s behavior is encoded declaratively in these paths and operations, without needing to write a single line of server-side code for the API definition itself. It’s a blueprint that tools can then use to generate client SDKs, server stubs, and interactive documentation.
When you define a path parameter like widgetId and specify its in: path and required: true, you’re telling the system that this value must be present in the URL. If a client tries to call /widgets/ without an ID, or if the server-side implementation doesn’t correctly extract and use that ID from the URL, the request will fail. This strictness is what makes APIs predictable.
The real power comes from how these definitions are used. A tool like Swagger UI can take this definition and render an interactive API explorer. A developer can then click "Try it out," fill in a widgetId like a1b2c3d4 for the /widgets/{widgetId} GET request, and see the live API response without writing any client code. This dramatically speeds up development and testing.
When you define schemas for your request and response bodies, like Widget, NewWidget, and UpdateWidget, you’re not just describing data; you’re enabling automatic validation. If a client sends a POST to /widgets with a JSON body that’s missing the required name field for NewWidget, the API framework (if configured correctly) can automatically reject the request with a 400 Bad Request error, citing the validation failure, before your application logic even runs.
The way path parameters are defined, including their schema and description, directly influences how servers must route and process incoming requests. A server framework will parse the incoming URL, match it against the defined paths, extract the values from the dynamic segments (like widgetId), and make them available to your handler function. If the type defined in the schema is integer but the client sends abc, the framework can often perform an automatic type coercion or throw an error.
Once you’ve mastered paths and operations, you’ll want to explore how to reuse definitions across your API using $ref for schemas and parameters, and how to handle different content types in request and response bodies.