OpenAPI Specification
Ahora necesitamos una especificación OpenAPI que describa el contrato de la API antes de escribir cualquier código de implementación. Dado un Film con la siguiente estructura:
{
"title": "ACADEMY DINOSAUR",
"description": "An Epic Drama of a Feminist And a Mad Scientist",
"releaseYear": 2006,
"rating": "PG",
"length": 86,
"language": "English",
"originalLanguage": "English",
"rentalDuration": 3,
"rentalRate": 4.99,
"replacementCost": 20.99,
"specialFeatures": "Trailers,Deleted Scenes",
"id": 42,
"lastUpdate": "2006-02-15T04:03:42Z"
}
Queremos que nuestra aplicación Spring Boot exponga CRUD endpoints.
Changed files
.
├── ...
└── src
├── main
│ ├── ...
│ └── resources
│ ├── ...
│ ├── openapi.yaml
│ └── ...
└── test
└── ...
OpenAPI Specification
OpenAPI Specification (OAS) define una interfaz estándar e independiente del lenguaje para las API HTTP, que permite tanto a humanos como a ordenadores descubrir y comprender las capacidades del servicio sin necesidad de acceder al código fuente, la documentación o inspeccionar el tráfico de red. Cuando está correctamente definida, un usuario puede comprender e interactuar con el servicio remoto con una mínima lógica de implementación.
- Java
- Kotlin
- Groovy
openapi: 3.0.3
info:
title: Sakila API
version: 1.0.0
description: API for the Sakila sample database - A DVD rental store management system
contact:
name: Pollito
url: https://springboot.pollito.tech/about/about-the-author
servers:
- url: http://localhost:8080/api
description: dev
- url: https://sakila-java.pollito.tech/api
description: prod
tags:
- name: Films
description: Endpoints related to films
paths:
/films:
get:
tags:
- Films
operationId: getFilms
summary: Lists films
description: Returns a standard response where the data property is a page of films matching the parameters criteria
parameters:
- name: page
in: query
description: Page number (0-based index)
schema:
type: integer
default: 0
minimum: 0
required: false
- name: size
in: query
description: Number of items per page
schema:
type: integer
default: 10
minimum: 1
maximum: 100
required: false
- name: sort
in: query
description: Sort criteria format `property,asc|desc`. Multiple sort parameters allowed
schema:
type: array
items:
type: string
example: "name,asc"
style: form
explode: true
required: false
responses:
'200':
description: Fetched page of films
content:
application/json:
schema:
$ref: '#/components/schemas/FilmListResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
tags:
- Films
operationId: createFilm
summary: Creates a new film
description: Creates a new film. Returns a standard response where the data property is the created film
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FilmFields'
responses:
'201':
description: Created Film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/films/{id}:
get:
tags:
- Films
operationId: getFilm
summary: Finds a film by id
description: Returns a standard response where the data property is the film with matching id
parameters:
- $ref: '#/components/parameters/filmId'
responses:
'200':
description: Fetched film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
put:
tags:
- Films
operationId: updateFilm
summary: Updates a film
description: Updates the fields of a film. Returns a standard response where the data property is the updated film
parameters:
- $ref: '#/components/parameters/filmId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FilmFields'
responses:
'200':
description: Updated film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- Films
operationId: deleteFilm
summary: Deletes a film
description: Deletes a film with matching id
parameters:
- $ref: '#/components/parameters/filmId'
responses:
'204':
description: Deleted film. No content is returned
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
parameters:
filmId:
name: id
in: path
description: Film unique identifier
required: true
schema:
type: integer
minimum: 1
schemas:
Error:
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
description: Standard error response
properties:
title:
type: string
description: HTTP Reason Phrase
example: 'Not Found'
detail:
type: string
description: Description of the problem
example: "No static resource for request '/'."
required:
- title
Film:
description: A film
allOf:
- $ref: '#/components/schemas/FilmFields'
- type: object
properties:
id:
type: integer
description: Unique identifier of the film
example: 42
minimum: 1
lastUpdate:
type: string
format: date-time
description: When the film record was last updated
example: '2006-02-15T04:03:42Z'
required:
- id
- lastUpdate
FilmFields:
type: object
description: Writable fields for a film (all required fields specified)
properties:
title:
type: string
description: Title of the film
example: 'ACADEMY DINOSAUR'
maxLength: 255
description:
type: string
description: Short synopsis of the film
example: 'An Epic Drama of a Feminist And a Mad Scientist'
maxLength: 255
releaseYear:
type: integer
description: Year the film was released
example: 2006
minimum: 1800
maximum: 2100
rating:
$ref: '#/components/schemas/FilmRating'
length:
type: integer
description: Duration of the film in minutes
example: 86
minimum: 1
language:
$ref: '#/components/schemas/FilmLanguage'
originalLanguage:
$ref: '#/components/schemas/FilmLanguage'
rentalDuration:
type: integer
description: Length of the rental period in days
example: 3
minimum: 0
rentalRate:
type: number
format: double
multipleOf: 0.01
description: Cost to rent the film for the rental period
example: 4.99
minimum: 0
replacementCost:
type: number
format: double
multipleOf: 0.01
description: Amount charged if the film is not returned or is returned damaged
example: 20.99
minimum: 0
specialFeatures:
type: string
description: Special features available on the film DVD
example: 'Trailers,Deleted Scenes'
maxLength: 255
required:
- title
- language
- rentalDuration
- rentalRate
- replacementCost
FilmLanguage:
type: string
enum: [English, Italian, Japanese, Mandarin, French, German]
description: Language used in the film
example: 'English'
FilmRating:
type: string
enum: [G, PG, PG-13, R, NC-17]
description: Motion Picture Association (MPA) rating
example: 'PG'
FilmListResponse:
description: Standard response where the data property is a Page of Films
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
properties:
data:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
content:
items:
$ref: "#/components/schemas/Film"
type: array
required:
- data
FilmResponse:
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
properties:
data:
$ref: '#/components/schemas/Film'
required:
- data
Page:
type: object
description: Sublist of a list of objects. It allows to gain information about the position of it in the containing entire list
properties:
content:
default: []
items: {}
type: array
pageable:
$ref: "#/components/schemas/Pageable"
totalElements:
default: 0
description: Total number of items that meet the criteria
example: 10
type: integer
totalPages:
default: 0
description: Total pages of items that meet the criteria
example: 10
type: integer
required:
- content
- pageable
- totalElements
- totalPages
Pageable:
type: object
description: Pagination information
properties:
pageNumber:
description: Current page number (starts from 0)
example: 0
minimum: 0
type: integer
pageSize:
description: Number of items retrieved on this page
example: 10
minimum: 0
type: integer
required:
- pageNumber
- pageSize
ResponseMetadata:
type: object
description: Standard response
properties:
instance:
type: string
description: API endpoint that was called
example: '/api/something'
status:
type: integer
description: HTTP status code
example: 200
minimum: 100
maximum: 599
timestamp:
type: string
format: date-time
description: ISO 8601 timestamp of when the response was generated
example: '2026-01-03T17:11:50.826722328Z'
trace:
type: string
description: Unique trace identifier for debugging purposes. Under unexpected situations it can be '00000000000000000000000000000000'
example: '9482c151-b417-43ff-9dbb-ee12b84e5d99'
required:
- instance
- timestamp
- trace
- status
openapi: 3.0.3
info:
title: Sakila API
version: 1.0.0
description: API for the Sakila sample database - A DVD rental store management system
contact:
name: Pollito
url: https://springboot.pollito.tech/about/about-the-author
servers:
- url: http://localhost:8080/api
description: dev
- url: https://sakila-kotlin.pollito.tech/api
description: prod
tags:
- name: Films
description: Endpoints related to films
paths:
/films:
get:
tags:
- Films
operationId: getFilms
summary: Lists films
description: Returns a standard response where the data property is a page of films matching the parameters criteria
parameters:
- name: page
in: query
description: Page number (0-based index)
schema:
type: integer
default: 0
minimum: 0
required: false
- name: size
in: query
description: Number of items per page
schema:
type: integer
default: 10
minimum: 1
maximum: 100
required: false
- name: sort
in: query
description: Sort criteria format `property,asc|desc`. Multiple sort parameters allowed
schema:
type: array
items:
type: string
example: "name,asc"
style: form
explode: true
required: false
responses:
'200':
description: Fetched page of films
content:
application/json:
schema:
$ref: '#/components/schemas/FilmListResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
tags:
- Films
operationId: createFilm
summary: Creates a new film
description: Creates a new film. Returns a standard response where the data property is the created film
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FilmFields'
responses:
'201':
description: Created Film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/films/{id}:
get:
tags:
- Films
operationId: getFilm
summary: Finds a film by id
description: Returns a standard response where the data property is the film with matching id
parameters:
- $ref: '#/components/parameters/filmId'
responses:
'200':
description: Fetched film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
put:
tags:
- Films
operationId: updateFilm
summary: Updates a film
description: Updates the fields of a film. Returns a standard response where the data property is the updated film
parameters:
- $ref: '#/components/parameters/filmId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FilmFields'
responses:
'200':
description: Updated film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- Films
operationId: deleteFilm
summary: Deletes a film
description: Deletes a film with matching id
parameters:
- $ref: '#/components/parameters/filmId'
responses:
'204':
description: Deleted film. No content is returned
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
parameters:
filmId:
name: id
in: path
description: Film unique identifier
required: true
schema:
type: integer
minimum: 1
schemas:
Error:
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
description: Standard error response
properties:
title:
type: string
description: HTTP Reason Phrase
example: 'Not Found'
detail:
type: string
description: Description of the problem
example: "No static resource for request '/'."
required:
- title
Film:
description: A film
allOf:
- $ref: '#/components/schemas/FilmFields'
- type: object
properties:
id:
type: integer
description: Unique identifier of the film
example: 42
minimum: 1
lastUpdate:
type: string
format: date-time
description: When the film record was last updated
example: '2006-02-15T04:03:42Z'
required:
- id
- lastUpdate
FilmFields:
type: object
description: Writable fields for a film (all required fields specified)
properties:
title:
type: string
description: Title of the film
example: 'ACADEMY DINOSAUR'
maxLength: 255
description:
type: string
description: Short synopsis of the film
example: 'An Epic Drama of a Feminist And a Mad Scientist'
maxLength: 255
releaseYear:
type: integer
description: Year the film was released
example: 2006
minimum: 1800
maximum: 2100
rating:
$ref: '#/components/schemas/FilmRating'
length:
type: integer
description: Duration of the film in minutes
example: 86
minimum: 1
language:
$ref: '#/components/schemas/FilmLanguage'
originalLanguage:
$ref: '#/components/schemas/FilmLanguage'
rentalDuration:
type: integer
description: Length of the rental period in days
example: 3
minimum: 0
rentalRate:
type: number
format: double
multipleOf: 0.01
description: Cost to rent the film for the rental period
example: 4.99
minimum: 0
replacementCost:
type: number
format: double
multipleOf: 0.01
description: Amount charged if the film is not returned or is returned damaged
example: 20.99
minimum: 0
specialFeatures:
type: string
description: Special features available on the film DVD
example: 'Trailers,Deleted Scenes'
maxLength: 255
required:
- title
- language
- rentalDuration
- rentalRate
- replacementCost
FilmLanguage:
type: string
enum: [English, Italian, Japanese, Mandarin, French, German]
description: Language used in the film
example: 'English'
FilmRating:
type: string
enum: [G, PG, PG-13, R, NC-17]
description: Motion Picture Association (MPA) rating
example: 'PG'
FilmListResponse:
description: Standard response where the data property is a Page of Films
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
properties:
data:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
content:
items:
$ref: "#/components/schemas/Film"
type: array
required:
- data
FilmResponse:
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
properties:
data:
$ref: '#/components/schemas/Film'
required:
- data
Page:
type: object
description: Sublist of a list of objects. It allows to gain information about the position of it in the containing entire list
properties:
content:
default: []
items: {}
type: array
pageable:
$ref: "#/components/schemas/Pageable"
totalElements:
default: 0
description: Total number of items that meet the criteria
example: 10
type: integer
totalPages:
default: 0
description: Total pages of items that meet the criteria
example: 10
type: integer
required:
- content
- pageable
- totalElements
- totalPages
Pageable:
type: object
description: Pagination information
properties:
pageNumber:
description: Current page number (starts from 0)
example: 0
minimum: 0
type: integer
pageSize:
description: Number of items retrieved on this page
example: 10
minimum: 0
type: integer
required:
- pageNumber
- pageSize
ResponseMetadata:
type: object
description: Standard response
properties:
instance:
type: string
description: API endpoint that was called
example: '/api/something'
status:
type: integer
description: HTTP status code
example: 200
minimum: 100
maximum: 599
timestamp:
type: string
format: date-time
description: ISO 8601 timestamp of when the response was generated
example: '2026-01-03T17:11:50.826722328Z'
trace:
type: string
description: Unique trace identifier for debugging purposes. Under unexpected situations it can be '00000000000000000000000000000000'
example: '9482c151-b417-43ff-9dbb-ee12b84e5d99'
required:
- instance
- timestamp
- trace
- status
openapi: 3.0.3
info:
title: Sakila API
version: 1.0.0
description: API for the Sakila sample database - A DVD rental store management system
contact:
name: Pollito
url: https://springboot.pollito.tech/about/about-the-author
servers:
- url: http://localhost:8080/api
description: dev
- url: https://sakila-groovy.pollito.tech/api
description: prod
tags:
- name: Films
description: Endpoints related to films
paths:
/films:
get:
tags:
- Films
operationId: getFilms
summary: Lists films
description: Returns a standard response where the data property is a page of films matching the parameters criteria
parameters:
- name: page
in: query
description: Page number (0-based index)
schema:
type: integer
default: 0
minimum: 0
required: false
- name: size
in: query
description: Number of items per page
schema:
type: integer
default: 10
minimum: 1
maximum: 100
required: false
- name: sort
in: query
description: Sort criteria format `property,asc|desc`. Multiple sort parameters allowed
schema:
type: array
items:
type: string
example: "name,asc"
style: form
explode: true
required: false
responses:
'200':
description: Fetched page of films
content:
application/json:
schema:
$ref: '#/components/schemas/FilmListResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
tags:
- Films
operationId: createFilm
summary: Creates a new film
description: Creates a new film. Returns a standard response where the data property is the created film
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FilmFields'
responses:
'201':
description: Created Film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/films/{id}:
get:
tags:
- Films
operationId: getFilm
summary: Finds a film by id
description: Returns a standard response where the data property is the film with matching id
parameters:
- $ref: '#/components/parameters/filmId'
responses:
'200':
description: Fetched film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
put:
tags:
- Films
operationId: updateFilm
summary: Updates a film
description: Updates the fields of a film. Returns a standard response where the data property is the updated film
parameters:
- $ref: '#/components/parameters/filmId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FilmFields'
responses:
'200':
description: Updated film
content:
application/json:
schema:
$ref: '#/components/schemas/FilmResponse'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- Films
operationId: deleteFilm
summary: Deletes a film
description: Deletes a film with matching id
parameters:
- $ref: '#/components/parameters/filmId'
responses:
'204':
description: Deleted film. No content is returned
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
parameters:
filmId:
name: id
in: path
description: Film unique identifier
required: true
schema:
type: integer
minimum: 1
schemas:
Error:
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
description: Standard error response
properties:
title:
type: string
description: HTTP Reason Phrase
example: 'Not Found'
detail:
type: string
description: Description of the problem
example: "No static resource for request '/'."
required:
- title
Film:
description: A film
allOf:
- $ref: '#/components/schemas/FilmFields'
- type: object
properties:
id:
type: integer
description: Unique identifier of the film
example: 42
minimum: 1
lastUpdate:
type: string
format: date-time
description: When the film record was last updated
example: '2006-02-15T04:03:42Z'
required:
- id
- lastUpdate
FilmFields:
type: object
description: Writable fields for a film (all required fields specified)
properties:
title:
type: string
description: Title of the film
example: 'ACADEMY DINOSAUR'
maxLength: 255
description:
type: string
description: Short synopsis of the film
example: 'An Epic Drama of a Feminist And a Mad Scientist'
maxLength: 255
releaseYear:
type: integer
description: Year the film was released
example: 2006
minimum: 1800
maximum: 2100
rating:
$ref: '#/components/schemas/FilmRating'
length:
type: integer
description: Duration of the film in minutes
example: 86
minimum: 1
language:
$ref: '#/components/schemas/FilmLanguage'
originalLanguage:
$ref: '#/components/schemas/FilmLanguage'
rentalDuration:
type: integer
description: Length of the rental period in days
example: 3
minimum: 0
rentalRate:
type: number
format: double
multipleOf: 0.01
description: Cost to rent the film for the rental period
example: 4.99
minimum: 0
replacementCost:
type: number
format: double
multipleOf: 0.01
description: Amount charged if the film is not returned or is returned damaged
example: 20.99
minimum: 0
specialFeatures:
type: string
description: Special features available on the film DVD
example: 'Trailers,Deleted Scenes'
maxLength: 255
required:
- title
- language
- rentalDuration
- rentalRate
- replacementCost
FilmLanguage:
type: string
enum: [English, Italian, Japanese, Mandarin, French, German]
description: Language used in the film
example: 'English'
FilmRating:
type: string
enum: [G, PG, PG-13, R, NC-17]
description: Motion Picture Association (MPA) rating
example: 'PG'
FilmListResponse:
description: Standard response where the data property is a Page of Films
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
properties:
data:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
content:
items:
$ref: "#/components/schemas/Film"
type: array
required:
- data
FilmResponse:
allOf:
- $ref: '#/components/schemas/ResponseMetadata'
- type: object
properties:
data:
$ref: '#/components/schemas/Film'
required:
- data
Page:
type: object
description: Sublist of a list of objects. It allows to gain information about the position of it in the containing entire list
properties:
content:
default: []
items: {}
type: array
pageable:
$ref: "#/components/schemas/Pageable"
totalElements:
default: 0
description: Total number of items that meet the criteria
example: 10
type: integer
totalPages:
default: 0
description: Total pages of items that meet the criteria
example: 10
type: integer
required:
- content
- pageable
- totalElements
- totalPages
Pageable:
type: object
description: Pagination information
properties:
pageNumber:
description: Current page number (starts from 0)
example: 0
minimum: 0
type: integer
pageSize:
description: Number of items retrieved on this page
example: 10
minimum: 0
type: integer
required:
- pageNumber
- pageSize
ResponseMetadata:
type: object
description: Standard response
properties:
instance:
type: string
description: API endpoint that was called
example: '/api/something'
status:
type: integer
description: HTTP status code
example: 200
minimum: 100
maximum: 599
timestamp:
type: string
format: date-time
description: ISO 8601 timestamp of when the response was generated
example: '2026-01-03T17:11:50.826722328Z'
trace:
type: string
description: Unique trace identifier for debugging purposes. Under unexpected situations it can be '00000000000000000000000000000000'
example: '9482c151-b417-43ff-9dbb-ee12b84e5d99'
required:
- instance
- timestamp
- trace
- status
Para una mejor visualización, podés copiar y pegar la especificación OpenAPI YAML del repositorio en Swagger Editor o usar OpenAPI (Swagger) Editor en IntelliJ IDEA.
El envelope pattern
Tal vez estás mirando los ejemplos de respuesta y pensando, "¿Qué onda con todo ese relleno extra? ¿Por qué envolver los datos reales de la película dentro de un objeto data?"
{
"instance": "/api/films/42",
"timestamp": "...",
"trace": "...",
"status": 200,
"data": {
"id": 42,
"title": "ACADEMY DINOSAUR",
// ...
}
}
Esa es una elección deliberada llamada Envelope Pattern. Y una vez que empezás a usarlo, no volvés atrás.
Pensalo como el correo físico. Cada pieza de correo que recibís, ya sea una tarjeta de cumpleaños o una factura, viene en un sobre. El sobre tiene información estándar: una dirección de remitente, una dirección de destino, un sello. El mensaje real está adentro.
Nuestras respuestas de API funcionan igual. El schema ResponseMetadata es nuestro sobre.
ResponseMetadata:
type: object
properties:
instance:
# ...
status:
# ...
timestamp:
# ...
trace:
# ...
Cada respuesta de la API, ya sea un éxito (200 OK) o un error (404 Not Found, 500 Internal Server Error, etc.), va a tener esta misma estructura de nivel superior.
- El payload real va adentro de la propiedad
datapara respuestas exitosas. - Para errores, reemplazamos
dataportitleydetailasí seguimos adheridos a Problem Details for HTTP APIs.
¿Para Qué Molestarse?
Consistencia. Ese es todo el juego.
- Sanidad del lado del cliente: El desarrollador que está construyendo el frontend o la app mobile puede escribir una pieza de código para manejar todas las respuestas de la API. Siempre sabe dónde encontrar el ID de
tracepara mostrarle a un usuario que está reportando un bug. Puede construir un manejador de errores genérico que siempre busquetitleydetail. No está adivinando si el payload es el objeto mismo, un array, o alguna forma rara de error. - A prueba de futuro: ¿Y si necesitamos agregar algún otro metadata? No hay problema. Simplemente agregamos un nuevo campo al nivel del envelope. La parte de
dataqueda intacta, y no rompemos la lógica de parsing del cliente para los datos reales de la película. - Se ve profesional: Una API con una estructura de respuesta consistente se siente sólida y bien pensada. Una inconsistente se siente amateur y es un dolor de cabeza para trabajar.
Paginación
TODO: explain pagination
Con la especificación OpenAPI y el envelope pattern en su lugar, el contrato de la API está completamente definido. El siguiente paso es generar código Spring Boot a partir de esta especificación para que la implementación coincida exactamente con el contrato.