{
  "openapi": "3.1.0",
  "info": {
    "title": "API pública de Lucas Cavalheri",
    "version": "1.0.0",
    "summary": "Dados do portfólio em JSON, somente leitura.",
    "description": "Expõe em JSON o mesmo conteúdo publicado em https://lucascavalheri.com.br: perfil, projetos,\nexperiência, stack e canais de contato. Não exige autenticação, não recebe\ndado nenhum e não tem efeito colateral: toda operação é GET e idempotente.\n\n## Versionamento\n\nA versão está no caminho: `/api/v1/`. Dentro de uma versão, só entram\nmudanças compatíveis — campo novo, valor novo, recurso novo. Nada de\nremover campo, renomear campo ou trocar tipo: isso exigiria `/api/v2/`.\n\n## Descontinuação\n\nUma versão a ser retirada passa a responder com os cabeçalhos `Deprecation`\ne `Sunset` (RFC 8594) e um `Link` com `rel=\"successor-version\"` apontando a\nsubstituta. O prazo mínimo entre o primeiro `Deprecation` e a retirada é de\n**180 dias**. O caminho sem versão, `/api/`, redireciona para a versão\ncorrente e por isso pode mudar de destino: prefira a URL versionada.\n\n## Limites\n\nCada resposta traz `RateLimit-Policy`, `RateLimit-Limit`, `RateLimit-Remaining`\ne `RateLimit-Reset`. A política é de 120 requisições por janela de\n60 segundos, por endereço de origem, aplicada na borda. Ao exceder,\na resposta é 429 com `Retry-After`.\n\n## Erros\n\nTodo erro segue a RFC 9457, em `application/problem+json`, com `type`,\n`title`, `status`, `detail`, `instance` e as extensões `codigo` (estável,\npara comparar em código) e `dica` (o que fazer em seguida).",
    "contact": {
      "name": "Lucas Cavalheri",
      "email": "lucas.dev.carvalho@gmail.com",
      "url": "https://lucascavalheri.com.br/contato"
    },
    "license": {
      "name": "CC BY 4.0",
      "identifier": "CC-BY-4.0"
    }
  },
  "servers": [
    {
      "url": "https://lucascavalheri.com.br/api/v1",
      "description": "Versão 1, estável"
    },
    {
      "url": "https://lucascavalheri.com.br/api",
      "description": "Sem versão: redireciona para a versão corrente"
    }
  ],
  "externalDocs": {
    "description": "Documentação para desenvolvedores",
    "url": "https://lucascavalheri.com.br/desenvolvedores"
  },
  "tags": [
    {
      "name": "descoberta",
      "description": "Índice da API e metadados"
    },
    {
      "name": "perfil",
      "description": "Quem é, onde está e para que serve"
    },
    {
      "name": "trabalho",
      "description": "Projetos e experiência profissional"
    },
    {
      "name": "contato",
      "description": "Como falar comigo"
    }
  ],
  "paths": {
    "/index.json": {
      "get": {
        "operationId": "obterIndiceApi",
        "summary": "Índice da API",
        "description": "Lista os recursos disponíveis, a versão corrente, onde está o OpenAPI e a política de uso. Comece por aqui quando não souber qual endpoint chamar.",
        "tags": [
          "descoberta"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Índice com recursos, versão e ponteiros de documentação",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política declarada, no formato da RFC de rate limit: 120;w=60",
                "schema": {
                  "type": "string",
                  "example": "120;w=60"
                }
              },
              "RateLimit-Limit": {
                "description": "Requisições permitidas na janela",
                "schema": {
                  "type": "integer",
                  "example": 120
                }
              },
              "RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual",
                "schema": {
                  "type": "integer",
                  "example": 119
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela reiniciar",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IndiceApi"
                }
              }
            }
          },
          "404": {
            "description": "O recurso pedido não existe nesta API.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "405": {
            "description": "A API só aceita GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido na janela atual.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos a esperar antes de tentar de novo",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Falha inesperada ao montar a resposta.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          }
        }
      }
    },
    "/perfil.json": {
      "get": {
        "operationId": "obterPerfil",
        "summary": "Perfil profissional",
        "description": "Nome, cargo, descrição, localização, empresa atual e as listas de quando faz sentido me chamar e quando não faz.",
        "tags": [
          "perfil"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Perfil com disponibilidade declarada",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política declarada, no formato da RFC de rate limit: 120;w=60",
                "schema": {
                  "type": "string",
                  "example": "120;w=60"
                }
              },
              "RateLimit-Limit": {
                "description": "Requisições permitidas na janela",
                "schema": {
                  "type": "integer",
                  "example": 120
                }
              },
              "RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual",
                "schema": {
                  "type": "integer",
                  "example": 119
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela reiniciar",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Perfil"
                }
              }
            }
          },
          "404": {
            "description": "O recurso pedido não existe nesta API.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "405": {
            "description": "A API só aceita GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido na janela atual.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos a esperar antes de tentar de novo",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Falha inesperada ao montar a resposta.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          }
        }
      }
    },
    "/projetos.json": {
      "get": {
        "operationId": "listarProjetos",
        "summary": "Projetos em destaque",
        "description": "Projetos com tipo, ano, descrição, stack e link do repositório quando o código é público.",
        "tags": [
          "trabalho"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Lista de projetos, do mais recente ao mais antigo",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política declarada, no formato da RFC de rate limit: 120;w=60",
                "schema": {
                  "type": "string",
                  "example": "120;w=60"
                }
              },
              "RateLimit-Limit": {
                "description": "Requisições permitidas na janela",
                "schema": {
                  "type": "integer",
                  "example": 120
                }
              },
              "RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual",
                "schema": {
                  "type": "integer",
                  "example": 119
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela reiniciar",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaProjetos"
                }
              }
            }
          },
          "404": {
            "description": "O recurso pedido não existe nesta API.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "405": {
            "description": "A API só aceita GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido na janela atual.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos a esperar antes de tentar de novo",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Falha inesperada ao montar a resposta.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          }
        }
      }
    },
    "/experiencia.json": {
      "get": {
        "operationId": "listarExperiencia",
        "summary": "Experiência profissional",
        "description": "Cargos, empresas, períodos, um resumo do que foi feito e a stack de cada um.",
        "tags": [
          "trabalho"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Cargos em ordem cronológica inversa",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política declarada, no formato da RFC de rate limit: 120;w=60",
                "schema": {
                  "type": "string",
                  "example": "120;w=60"
                }
              },
              "RateLimit-Limit": {
                "description": "Requisições permitidas na janela",
                "schema": {
                  "type": "integer",
                  "example": 120
                }
              },
              "RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual",
                "schema": {
                  "type": "integer",
                  "example": 119
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela reiniciar",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaExperiencia"
                }
              }
            }
          },
          "404": {
            "description": "O recurso pedido não existe nesta API.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "405": {
            "description": "A API só aceita GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido na janela atual.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos a esperar antes de tentar de novo",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Falha inesperada ao montar a resposta.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          }
        }
      }
    },
    "/stack.json": {
      "get": {
        "operationId": "listarStack",
        "summary": "Stack por categoria",
        "description": "Tecnologias agrupadas por categoria, com o site oficial de cada uma. A lista completa está em /usos.",
        "tags": [
          "perfil"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Categorias com suas tecnologias",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política declarada, no formato da RFC de rate limit: 120;w=60",
                "schema": {
                  "type": "string",
                  "example": "120;w=60"
                }
              },
              "RateLimit-Limit": {
                "description": "Requisições permitidas na janela",
                "schema": {
                  "type": "integer",
                  "example": 120
                }
              },
              "RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual",
                "schema": {
                  "type": "integer",
                  "example": 119
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela reiniciar",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaStack"
                }
              }
            }
          },
          "404": {
            "description": "O recurso pedido não existe nesta API.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "405": {
            "description": "A API só aceita GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido na janela atual.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos a esperar antes de tentar de novo",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Falha inesperada ao montar a resposta.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          }
        }
      }
    },
    "/contato.json": {
      "get": {
        "operationId": "obterContato",
        "summary": "Canais de contato",
        "description": "E-mail, telefone, link de WhatsApp com mensagem pronta, redes e o tempo de resposta esperado.",
        "tags": [
          "contato"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Canais e tempo de resposta",
            "headers": {
              "RateLimit-Policy": {
                "description": "Política declarada, no formato da RFC de rate limit: 120;w=60",
                "schema": {
                  "type": "string",
                  "example": "120;w=60"
                }
              },
              "RateLimit-Limit": {
                "description": "Requisições permitidas na janela",
                "schema": {
                  "type": "integer",
                  "example": 120
                }
              },
              "RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual",
                "schema": {
                  "type": "integer",
                  "example": 119
                }
              },
              "RateLimit-Reset": {
                "description": "Segundos até a janela reiniciar",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contato"
                }
              }
            }
          },
          "404": {
            "description": "O recurso pedido não existe nesta API.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "405": {
            "description": "A API só aceita GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido na janela atual.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos a esperar antes de tentar de novo",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Falha inesperada ao montar a resposta.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problema"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {},
    "schemas": {
      "Problema": {
        "type": "object",
        "description": "Erro no formato RFC 9457, com duas extensões próprias.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "codigo",
          "dica"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI que identifica e documenta o tipo do erro",
            "example": "https://lucascavalheri.com.br/desenvolvedores#recurso-nao-encontrado"
          },
          "title": {
            "type": "string",
            "description": "Resumo legível do tipo do erro"
          },
          "status": {
            "type": "integer",
            "description": "Código HTTP repetido no corpo",
            "example": 404
          },
          "detail": {
            "type": "string",
            "description": "Explicação desta ocorrência"
          },
          "instance": {
            "type": "string",
            "description": "Caminho que produziu o erro"
          },
          "codigo": {
            "type": "string",
            "description": "Extensão: identificador estável, seguro para comparar em código",
            "example": "recurso_nao_encontrado"
          },
          "dica": {
            "type": "string",
            "description": "Extensão: o que fazer em seguida"
          },
          "documentacao": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Tecnologia": {
        "type": "object",
        "required": [
          "id",
          "nome",
          "site"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador curto",
            "example": "typescript"
          },
          "nome": {
            "type": "string",
            "example": "TypeScript"
          },
          "site": {
            "type": "string",
            "format": "uri",
            "description": "Site oficial da tecnologia"
          }
        }
      },
      "Localizacao": {
        "type": "object",
        "required": [
          "cidade",
          "estado",
          "pais"
        ],
        "properties": {
          "cidade": {
            "type": "string",
            "example": "Mogi Mirim"
          },
          "estado": {
            "type": "string",
            "example": "SP"
          },
          "pais": {
            "type": "string",
            "example": "BR"
          },
          "atendimento": {
            "type": "string"
          }
        }
      },
      "Empresa": {
        "type": "object",
        "required": [
          "nome"
        ],
        "properties": {
          "nome": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "site": {
            "type": "string"
          }
        }
      },
      "Perfil": {
        "type": "object",
        "required": [
          "nome",
          "cargo",
          "descricao",
          "localizacao"
        ],
        "properties": {
          "nome": {
            "type": "string",
            "example": "Lucas Cavalheri"
          },
          "nomeCompleto": {
            "type": "string"
          },
          "cargo": {
            "type": "string",
            "example": "Desenvolvedor Web Full Stack Pleno"
          },
          "descricao": {
            "type": "string"
          },
          "localizacao": {
            "$ref": "#/components/schemas/Localizacao"
          },
          "empresaAtual": {
            "$ref": "#/components/schemas/Empresa"
          },
          "disponivelPara": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "naoAtende": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "curriculo": {
            "type": "string",
            "format": "uri"
          },
          "site": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Projeto": {
        "type": "object",
        "required": [
          "nome",
          "tipo",
          "ano",
          "descricao",
          "tecnologias"
        ],
        "properties": {
          "nome": {
            "type": "string",
            "example": "EduConnect"
          },
          "tipo": {
            "type": "string",
            "example": "Produto próprio"
          },
          "ano": {
            "type": "string",
            "example": "2026"
          },
          "descricao": {
            "type": "string"
          },
          "repositorio": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "site": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "codigoPrivado": {
            "type": "boolean"
          },
          "tecnologias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tecnologia"
            }
          }
        }
      },
      "ListaProjetos": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Projeto"
        }
      },
      "Experiencia": {
        "type": "object",
        "required": [
          "cargo",
          "empresa",
          "periodo",
          "tecnologias"
        ],
        "properties": {
          "cargo": {
            "type": "string"
          },
          "empresa": {
            "$ref": "#/components/schemas/Empresa"
          },
          "periodo": {
            "type": "string",
            "example": "2024 — hoje"
          },
          "resumo": {
            "type": "string"
          },
          "tecnologias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tecnologia"
            }
          }
        }
      },
      "ListaExperiencia": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Experiencia"
        }
      },
      "GrupoStack": {
        "type": "object",
        "required": [
          "categoria",
          "itens"
        ],
        "properties": {
          "categoria": {
            "type": "string",
            "example": "Back-end"
          },
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tecnologia"
            }
          }
        }
      },
      "ListaStack": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/GrupoStack"
        }
      },
      "CanalContato": {
        "type": "object",
        "required": [
          "tipo",
          "rotulo",
          "valor"
        ],
        "properties": {
          "tipo": {
            "type": "string",
            "example": "whatsapp"
          },
          "rotulo": {
            "type": "string",
            "example": "WhatsApp"
          },
          "valor": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Contato": {
        "type": "object",
        "required": [
          "email",
          "canais"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "telefone": {
            "type": "string",
            "example": "+5519999031230"
          },
          "whatsapp": {
            "type": "string",
            "format": "uri"
          },
          "tempoDeResposta": {
            "type": "string",
            "example": "Até um dia útil"
          },
          "canais": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CanalContato"
            }
          }
        }
      },
      "Recurso": {
        "type": "object",
        "required": [
          "rota",
          "descricao"
        ],
        "properties": {
          "rota": {
            "type": "string",
            "example": "/api/v1/perfil.json"
          },
          "descricao": {
            "type": "string"
          }
        }
      },
      "PaginaSite": {
        "type": "object",
        "required": [
          "rota",
          "titulo"
        ],
        "properties": {
          "rota": {
            "type": "string"
          },
          "titulo": {
            "type": "string"
          },
          "resumo": {
            "type": "string"
          }
        }
      },
      "IndiceApi": {
        "type": "object",
        "required": [
          "nome",
          "versao",
          "recursos"
        ],
        "properties": {
          "nome": {
            "type": "string"
          },
          "versao": {
            "type": "string",
            "example": "1.0.0"
          },
          "descricao": {
            "type": "string"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          },
          "documentacao": {
            "type": "string",
            "format": "uri"
          },
          "autenticacao": {
            "type": "string",
            "example": "nenhuma"
          },
          "versionamento": {
            "type": "object",
            "properties": {
              "atual": {
                "type": "string",
                "example": "v1"
              },
              "caminho": {
                "type": "string",
                "example": "https://lucascavalheri.com.br/api/v1"
              },
              "politica": {
                "type": "string",
                "format": "uri"
              },
              "descontinuacao": {
                "type": "string"
              }
            }
          },
          "limiteDeUso": {
            "type": "object",
            "properties": {
              "requisicoes": {
                "type": "integer",
                "example": 120
              },
              "janelaSegundos": {
                "type": "integer",
                "example": 60
              },
              "cabecalhos": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "recursos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recurso"
            }
          },
          "paginas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaginaSite"
            }
          }
        }
      }
    }
  },
  "security": []
}