---
title: "OpenAPI e Swagger no Ktor: Documente APIs Kotlin | Kotlin Brasil"
url: "https://kotlin.dev.br/tutoriais/kotlin-ktor-openapi-swagger/"
markdown_url: "https://kotlin.dev.br/tutoriais/kotlin-ktor-openapi-swagger.MD"
description: "Aprenda a documentar APIs Ktor com OpenAPI e Swagger UI em Kotlin: contrato, rotas, exemplos, versionamento e boas práticas para backend."
date: "2026-05-19"
author: "Karina Melo"
---

# OpenAPI e Swagger no Ktor: Documente APIs Kotlin | Kotlin Brasil

Aprenda a documentar APIs Ktor com OpenAPI e Swagger UI em Kotlin: contrato, rotas, exemplos, versionamento e boas práticas para backend.


Documentar uma API Ktor com **OpenAPI** não é burocracia: é uma forma de transformar rotas, DTOs, códigos de status e regras de autenticação em um contrato que frontend, mobile, QA e integrações externas conseguem consultar sem adivinhar o comportamento do backend. Em times pequenos, isso evita mensagens soltas no chat. Em times maiores, vira base para SDKs, testes de contrato e versionamento.

Este tutorial mostra um caminho prático para usar **Swagger UI e OpenAPI em Ktor** sem perder o estilo idiomático do Kotlin. A ideia é começar com um contrato simples, expor uma documentação navegável, manter exemplos úteis e conectar o tema com o que você já usa em [REST APIs com Kotlin](/guias/guia-kotlin-rest-api/), [Ktor para backend](/guias/guia-kotlin-backend-ktor/) e [testes automatizados](/guias/guia-testes-kotlin/).

## Quando vale documentar uma API Ktor com OpenAPI?

Vale documentar quando a API tem mais de um consumidor, quando uma pessoa nova precisa entender o contrato rapidamente ou quando mudanças de resposta podem quebrar outro sistema. Mesmo em um projeto de portfólio, uma página Swagger bem feita passa uma mensagem forte: você sabe construir backend pensando em consumo real, não apenas em rotas que funcionam localmente.

Os sinais de que OpenAPI deve entrar no projeto são claros:

- o app Android, web ou serviço parceiro consome a API;
- existem endpoints privados e públicos com autenticação diferente;
- DTOs mudam com frequência e precisam de exemplos;
- QA precisa saber quais erros esperar;
- você quer gerar clientes HTTP ou validar contrato em CI;
- há vagas ou entrevistas pedindo experiência com APIs documentadas.

Se o projeto ainda está no começo, não espere “ficar grande” para documentar. Um contrato pequeno e atualizado é melhor que uma documentação enorme escrita depois, quando ninguém lembra por que cada endpoint existe.

## Modelo mental: contrato primeiro, implementação sempre perto

OpenAPI descreve a superfície HTTP da sua aplicação: caminhos, métodos, parâmetros, corpo de requisição, respostas, schemas, autenticação e metadados. O erro comum é tratar o arquivo `openapi.yaml` como um artefato separado que envelhece longe do código.

No Ktor, pense em três camadas:

1. **rotas Ktor** definem o comportamento real;
2. **DTOs Kotlin** representam entrada e saída;
3. **contrato OpenAPI** explica como consumidores devem chamar a API.

O objetivo é manter essas três camadas próximas. Se você usa geração automática disponível na versão do seu stack, ótimo. Se ainda mantém YAML manual, coloque o arquivo no repositório, revise junto do código e valide no pipeline. O ponto não é a ferramenta perfeita; é impedir que contrato e implementação se contradigam.

## Dependências básicas para Swagger UI em Ktor

O ecossistema Ktor permite diferentes abordagens. Em muitos projetos, você verá uma combinação de plugin de OpenAPI/Swagger, arquivo YAML servido pela aplicação e uma rota de interface visual. O exemplo abaixo usa uma estrutura didática: mantenha `openapi/documentation.yaml` como fonte do contrato e exponha Swagger UI em `/swagger`.

```kotlin
// build.gradle.kts
plugins {
    kotlin("jvm") version "2.3.20"
    id("io.ktor.plugin") version "3.4.0"
    kotlin("plugin.serialization") version "2.3.20"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.ktor:ktor-server-core-jvm")
    implementation("io.ktor:ktor-server-netty-jvm")
    implementation("io.ktor:ktor-server-content-negotiation-jvm")
    implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")

    // Use os artefatos de OpenAPI/Swagger compatíveis com a sua versão do Ktor.
    implementation("io.ktor:ktor-server-openapi")
    implementation("io.ktor:ktor-server-swagger")

    testImplementation("io.ktor:ktor-server-tests-jvm")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
```

Antes de copiar versões para produção, confira a documentação oficial e a versão usada pelo seu projeto. Se você está migrando de Ktor 2.x para Ktor 3.x, leia também o resumo de [Ktor 3.4 com OpenAPI e streaming](/blog/ktor-3-4-novidades-openapi-streaming-2026/) para entender o que mudou no suporte de documentação.

## Criando uma API de exemplo

Vamos usar uma API de tarefas porque ela é pequena o bastante para caber em um tutorial, mas real o suficiente para demonstrar listagem, criação, erros e autenticação futura.

```kotlin
import kotlinx.serialization.Serializable

@Serializable
data class TarefaResponse(
    val id: Long,
    val titulo: String,
    val concluida: Boolean
)

@Serializable
data class CriarTarefaRequest(
    val titulo: String
)

@Serializable
data class ErroResponse(
    val codigo: String,
    val mensagem: String
)
```

A rota Ktor pode começar simples:

```kotlin
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun Application.tarefaRoutes() {
    val tarefas = mutableListOf(
        TarefaResponse(1, "Documentar API Ktor", false)
    )

    routing {
        route("/api/v1/tarefas") {
            get {
                call.respond(tarefas)
            }

            post {
                val request = call.receive<CriarTarefaRequest>()

                if (request.titulo.isBlank()) {
                    call.respond(
                        HttpStatusCode.BadRequest,
                        ErroResponse(
                            codigo = "titulo_obrigatorio",
                            mensagem = "Informe um título para a tarefa."
                        )
                    )
                    return@post
                }

                val nova = TarefaResponse(
                    id = tarefas.size.toLong() + 1,
                    titulo = request.titulo,
                    concluida = false
                )
                tarefas += nova

                call.respond(HttpStatusCode.Created, nova)
            }
        }
    }
}
```

Essa implementação é propositalmente simples. Em uma aplicação real, você provavelmente teria service, repository, banco de dados com [Exposed](/blog/kotlin-exposed-orm-framework-sql/) ou outro mecanismo de persistência, além de validação mais completa. O contrato OpenAPI precisa refletir o comportamento observável, não a organização interna.

## Escrevendo o primeiro openapi.yaml

Crie um arquivo como `src/main/resources/openapi/documentation.yaml`. Ele deve começar com metadados claros e schemas pequenos.

```yaml
openapi: 3.1.0
info:
  title: API de Tarefas Kotlin Brasil
  version: 1.0.0
  description: API de exemplo construída com Ktor e Kotlin.
servers:
  - url: https://api.exemplo.com
    description: Produção
  - url: http://localhost:8080
    description: Desenvolvimento local
paths:
  /api/v1/tarefas:
    get:
      summary: Lista tarefas
      operationId: listarTarefas
      tags: [Tarefas]
      responses:
        "200":
          description: Lista de tarefas retornada com sucesso.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/TarefaResponse"
              examples:
                exemplo:
                  value:
                    - id: 1
                      titulo: Documentar API Ktor
                      concluida: false
    post:
      summary: Cria uma tarefa
      operationId: criarTarefa
      tags: [Tarefas]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CriarTarefaRequest"
      responses:
        "201":
          description: Tarefa criada.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TarefaResponse"
        "400":
          description: Requisição inválida.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErroResponse"
components:
  schemas:
    CriarTarefaRequest:
      type: object
      required: [titulo]
      properties:
        titulo:
          type: string
          minLength: 1
          example: Revisar contrato OpenAPI
    TarefaResponse:
      type: object
      required: [id, titulo, concluida]
      properties:
        id:
          type: integer
          format: int64
          example: 1
        titulo:
          type: string
          example: Documentar API Ktor
        concluida:
          type: boolean
          example: false
    ErroResponse:
      type: object
      required: [codigo, mensagem]
      properties:
        codigo:
          type: string
          example: titulo_obrigatorio
        mensagem:
          type: string
          example: Informe um título para a tarefa.
```

Esse YAML já entrega valor: qualquer pessoa consegue abrir o contrato e entender como listar e criar tarefas. Repare que os exemplos não são enfeite. Eles ajudam consumidores a saber formato, nomes de campos, tipos e respostas esperadas.

## Expondo Swagger UI no Ktor

Com o contrato dentro de `resources`, exponha uma rota para a documentação. O formato exato pode variar conforme a versão do Ktor e o plugin escolhido, mas a ideia é esta:

```kotlin
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.swagger.*
import io.ktor.server.routing.*

fun Application.module() {
    install(ContentNegotiation) {
        json()
    }

    tarefaRoutes()

    routing {
        swaggerUI(
            path = "swagger",
            swaggerFile = "openapi/documentation.yaml"
        )
    }
}
```

Depois de subir a aplicação, acesse `http://localhost:8080/swagger`. A interface deve mostrar os endpoints, schemas e exemplos definidos no contrato.

Se você preferir servir apenas o JSON ou YAML para uma ferramenta externa, exponha um endpoint como `/openapi.yaml` e publique a UI em outro lugar. O importante é que o contrato esteja disponível no mesmo ciclo de deploy da API.

## Documentando erros e autenticação

Um contrato útil não mostra apenas o caminho feliz. Ele documenta erros comuns, códigos de status e regras de autenticação. Para APIs com JWT, adicione um security scheme:

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

Em endpoints públicos, você pode sobrescrever com `security: []`. Em endpoints privados, declare respostas como `401` e `403` com payload de erro. Isso evita uma frustração comum: o frontend descobrir em runtime que a API retorna um formato de erro diferente em cada rota.

Para Ktor, alinhe o contrato com o bloco de autenticação:

```kotlin
import io.ktor.server.auth.*
import io.ktor.server.routing.*

fun Application.rotasPrivadas() {
    routing {
        authenticate("auth-jwt") {
            get("/api/v1/perfil") {
                // resposta do perfil autenticado
            }
        }
    }
}
```

Se a rota está dentro de `authenticate`, o OpenAPI deve deixar isso explícito. Se a rota aceita chamadas anônimas, não marque como protegida por acidente.

## Mantendo DTOs e schemas sincronizados

O maior risco de OpenAPI manual é divergência. Para reduzir isso, crie uma rotina simples:

- toda PR que muda request ou response precisa alterar o contrato;
- exemplos devem ser atualizados junto com os DTOs;
- códigos de erro precisam ter nome estável;
- campos removidos devem ser tratados como breaking change;
- o pipeline deve validar o YAML.

Você pode usar ferramentas como `swagger-cli`, `redocly`, `spectral` ou geradores de cliente em CI. Mesmo que não gere código, validar o arquivo já captura indentação quebrada, `$ref` inválido e schemas incompletos.

Um teste simples no projeto também ajuda:

```kotlin
import kotlin.test.Test
import kotlin.test.assertTrue

class OpenApiContractTest {
    @Test
    fun `contrato openapi deve existir no classpath`() {
        val resource = javaClass.classLoader
            .getResource("openapi/documentation.yaml")

        assertTrue(resource != null, "Contrato OpenAPI não encontrado")
    }
}
```

Esse teste não valida todo o contrato, mas evita o erro básico de esquecer o arquivo fora do pacote final. Para validação forte, combine com uma ferramenta especializada no pipeline de [CI/CD para Kotlin](/guias/guia-kotlin-ci-cd/).

## Versionamento de API e contrato

Versionar API não é apenas colocar `/v1` na URL. O contrato deve indicar a versão do documento, e mudanças incompatíveis precisam de cuidado. Exemplos de breaking changes:

- remover campo de resposta usado por clientes;
- mudar tipo de `id` de número para string;
- tornar obrigatório um campo antes opcional;
- alterar o formato de erro;
- trocar códigos HTTP sem compatibilidade.

Mudanças aditivas geralmente são seguras: adicionar campo opcional, novo endpoint, novo valor em enum documentado como extensível. Em Kotlin, prefira DTOs explícitos de request e response em vez de expor entidades internas diretamente. Isso facilita evoluir o banco sem quebrar contrato público.

## Checklist para uma documentação Ktor que ajuda de verdade

Antes de considerar sua documentação pronta, passe por este checklist:

- a página Swagger abre em ambiente local;
- cada endpoint tem `summary`, `operationId` e tag;
- requests têm exemplos realistas;
- respostas de sucesso e erro estão documentadas;
- autenticação está clara;
- campos obrigatórios e opcionais estão corretos;
- DTOs não expõem senha, token interno ou detalhes de banco;
- o contrato passa em validação automatizada;
- a documentação é atualizada no mesmo commit da mudança de rota.

Esse nível de disciplina já coloca seu projeto acima da média de muitos backends de portfólio.

## Erros comuns ao usar Swagger com Ktor

**1. Documentar só 200 OK.** APIs reais falham. Mostre `400`, `401`, `403`, `404`, `409` e `500` quando fizer sentido.

**2. Copiar DTO interno para o contrato.** Entidades de banco e DTOs públicos têm responsabilidades diferentes. O contrato deve representar o que o consumidor pode depender.

**3. Deixar exemplos genéricos demais.** `string` e `example` vazio não ajudam. Use dados parecidos com o domínio real.

**4. Esquecer autenticação por rota.** Uma UI Swagger linda não resolve nada se o consumidor não sabe quando enviar bearer token.

**5. Não validar em CI.** YAML quebrado geralmente só aparece quando alguém tenta abrir a documentação. Valide antes do deploy.

## Próximos passos

Depois que a documentação estiver funcionando, evolua para testes de contrato e geração de cliente. Um app Android, por exemplo, pode usar um cliente gerado ou pelo menos validar que os modelos esperados batem com o contrato publicado. Para backend, combine OpenAPI com [testes em Kotlin](/guias/guia-testes-kotlin/), [observabilidade](/blog/kotlin-observabilidade/) e [Docker](/guias/guia-kotlin-docker/) para fechar o ciclo profissional.

Se seu foco é comparar stacks, vale ver como a documentação de APIs aparece em <a href="https://python.dev.br/guias/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">FastAPI e Django REST em Python</a> e em <a href="https://golang.com.br/guias/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">frameworks Go para APIs</a>. O vocabulário muda, mas a responsabilidade é a mesma: contrato claro, exemplos úteis e mudança controlada.

## FAQ rápido

### Ktor gera OpenAPI automaticamente?

Depende da versão e dos plugins usados no projeto. O suporte evoluiu bastante, mas muitos times ainda mantêm um `openapi.yaml` revisado junto do código. O melhor caminho é escolher uma abordagem que seu time consiga manter sem deixar a documentação envelhecer.

### Swagger UI deve ficar público em produção?

Depende do produto. Para API pública, normalmente sim. Para API interna, você pode proteger a rota, expor apenas na intranet ou publicar a documentação em um portal separado. Nunca exponha endpoints administrativos ou exemplos com dados sensíveis.

### OpenAPI substitui testes?

Não. OpenAPI descreve o contrato esperado. Testes verificam se a implementação cumpre esse contrato. O ideal é usar os dois: documentação para consumo humano e validação automatizada para evitar regressões.

### Preciso usar annotations?

Não necessariamente. Uma vantagem do Kotlin com Ktor é manter rotas em DSL. Você pode documentar por YAML, por DSL de documentação ou por plugins que aproximam contrato e rota. Evite escolher uma solução apenas porque parece “mais automática”; escolha a que fica sustentável no repositório.
