{"openapi":"3.1.0","info":{"title":"My Finance API","description":"## Visão geral\n\nAPI REST do **My Finance** — plataforma de finanças pessoais com Open Finance\n(Pluggy), conciliação manual × importada, relatórios, projeções e cartões.\n\n> Documentação única em **Swagger UI** (esta página). O contrato cru continua\n> disponível em [`/openapi.json`](/openapi.json) para gerar clientes (Postman,\n> Insomnia, OpenAPI Generator).\n\n---\n\n## Autenticação\n\nTodos os endpoints de negócio exigem um **Bearer Token JWT** no header:\n\n```\nAuthorization: Bearer <access_token>\n```\n\n### Fluxo de obtenção do token\n\n1. **Cadastro** — `POST /v1/auth/register` (envia e-mail de verificação).\n2. **Verificação de e-mail** — `POST /v1/auth/verify-email` com o `token`\n   recebido por e-mail (em `development` o token vem na resposta do registro).\n3. **Login** — `POST /v1/auth/login` retorna `access_token` (curto) e\n   `refresh_token` (longo).\n4. **Renovação** — `POST /v1/auth/refresh` quando o `access_token` expirar.\n\n### Como autenticar no Swagger UI\n\n1. Clique em **Authorize** (cadeado) no topo desta página.\n2. Cole apenas o `access_token` (sem o prefixo `Bearer`).\n3. Confirme — o cadeado fica fechado e todos os endpoints protegidos passam a\n   enviar o header automaticamente.\n\n### Recuperação de senha\n\n- `POST /v1/auth/forgot-password` — envia link de redefinição (sempre devolve\n  mensagem genérica para não vazar quais e-mails existem).\n- `POST /v1/auth/reset-password` — troca a senha usando o token enviado.\n\n---\n\n## Convenções\n\n| Tópico              | Regra                                                                   |\n|---------------------|-------------------------------------------------------------------------|\n| Datas               | `YYYY-MM-DD` (ISO 8601). Datetimes em UTC.                              |\n| Moeda               | Valores monetários como número decimal positivo. `currency` ISO 4217.    |\n| Identificadores     | `int` autoincremento por entidade. UUIDs apenas em integrações externas. |\n| Multitenancy        | Toda consulta é filtrada pelo usuário do token.                          |\n| Paginação           | A maior parte das listas ainda não pagina (planejado).                   |\n| Erros               | Sempre `{\"detail\": \"<mensagem>\"}` em pt-BR.                              |\n| Idempotência Pluggy | Webhooks usam `eventId` ou hash do corpo (chave única).                  |\n\n### Códigos de status comuns\n\n| Código | Quando ocorre                                                          |\n|--------|------------------------------------------------------------------------|\n| `200`  | Sucesso em GET/PUT/POST que retorna conteúdo.                          |\n| `201`  | Recurso criado (POSTs de criação).                                     |\n| `202`  | Aceito para processamento assíncrono (webhook Pluggy).                 |\n| `204`  | Sucesso sem corpo (DELETE / change-password).                          |\n| `400`  | Requisição malformada ou regra de negócio violada (token inválido).    |\n| `401`  | Token ausente, expirado ou inválido.                                   |\n| `403`  | Token válido mas e-mail não verificado.                                |\n| `404`  | Recurso inexistente ou não pertence ao usuário do token.               |\n| `409`  | Conflito (e-mail já cadastrado, categoria duplicada, FK em uso).        |\n| `422`  | Validação Pydantic / regra de domínio (ex.: parcelas inválidas).        |\n| `503`  | Configuração de servidor faltando (`PLUGGY_ENCRYPTION_KEY` em prod).   |\n\n---\n\n## Ambientes\n\n- **Production** — `https://apimyfinance.marlonsantos.dev`\n- **Development** — `http://127.0.0.1:8000`\n\nEm `development`:\n- Endpoints de cadastro/forgot retornam o token também no corpo da resposta\n  (campos `verification_token` e `reset_token`) para facilitar testes.\n- CORS aberto (`*`).\n\n---\n\n## Integração com Pluggy (Open Finance)\n\nFluxo BYOK (cliente fornece `clientId`/`clientSecret` da própria conta Pluggy):\n\n1. `POST /v1/pluggy/credentials` — salva as credenciais (criptografadas com\n   Fernet usando `PLUGGY_ENCRYPTION_KEY`).\n2. Após o usuário concluir o **Pluggy Connect** no app Flutter, o `itemId`\n   gerado é vinculado a uma `Account` ou `CreditCard` (via `pluggy_item_id`).\n3. `POST /v1/pluggy/sync` — busca contas e transações dos `item_ids`\n   informados (ou descobre pelos vinculados em `Account`/`CreditCard`).\n4. **Webhook automático** — `POST /v1/pluggy/webhook` recebe eventos\n   (`item/updated`, `transactions/created`, etc.) e dispara sync incremental\n   em background. Autenticação opcional via `Authorization: Bearer <secret>`\n   ou `X-Pluggy-Signature` (HMAC-SHA256).\n5. **Conciliação** — sugestões em `GET /v1/reconciliation/suggestions`,\n   confirmação em `POST /v1/reconciliation/matches` e rejeição em\n   `POST /v1/reconciliation/rejections`.\n\n> Detalhes do mapeamento Pluggy → domínio (BANK → `Account`, CREDIT →\n> `CreditCard`, parcelas, fuso BRT) estão documentados no endpoint\n> `POST /v1/pluggy/sync`.\n\n---\n\n## Suporte e contato\n\n- Issues: <https://github.com/agnconsultoria/my_finance/issues>\n- Repositório: <https://github.com/agnconsultoria/my_finance>","version":"0.1.0"},"servers":[{"url":"https://apimyfinance.marlonsantos.dev","description":"Produção"},{"url":"http://127.0.0.1:8000","description":"Desenvolvimento local"}],"paths":{"/v1/auth/register":{"post":{"tags":["auth"],"summary":"Cadastrar novo usuário","description":"Cria um usuário **não verificado** e dispara um e-mail com o token de verificação. O usuário só pode fazer login após confirmar o e-mail em `POST /v1/auth/verify-email`.\n\n**Comportamento por ambiente:**\n- `development` / `staging`: o token é devolvido também no corpo (`verification_token`) para facilitar testes manuais.\n- `production`: o token é enviado exclusivamente por e-mail.\n\n**Erros:**\n- `409 Conflict` — e-mail já cadastrado.\n- `422 Unprocessable Entity` — e-mail inválido ou senha fora dos limites.","operationId":"register_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Usuário recém-criado, aguardando verificação de e-mail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"}}}},"409":{"description":"E-mail já cadastrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"E-mail já cadastrado."}}}},"400":{"description":"Requisição inválida (token de verificação/reset inválido, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}}}},"/v1/auth/verify-email":{"post":{"tags":["auth"],"summary":"Verificar e-mail","description":"Confirma o e-mail do usuário usando o JWT de verificação enviado no cadastro. Após o sucesso, o usuário pode realizar login.\n\n**Erros:**\n- `400 Bad Request` — token inválido, expirado ou e-mail divergente.","operationId":"verify_email_v1_auth_verify_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailRequest"}}},"required":true},"responses":{"200":{"description":"Mensagem de sucesso da verificação.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"description":"Requisição inválida (token de verificação/reset inválido, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}}}},"/v1/auth/login":{"post":{"tags":["auth"],"summary":"Login (obter par de tokens)","description":"Autentica o usuário e retorna um par **access + refresh**.\n\n- O `access_token` deve ser usado no header `Authorization: Bearer <token>` em todos os endpoints protegidos.\n- O `refresh_token` é usado em `POST /v1/auth/refresh` para obter um novo par sem precisar relogar.\n\n**Pré-requisito:** e-mail já verificado (`POST /v1/auth/verify-email`).\n\n**Erros:**\n- `401 Unauthorized` — credenciais inválidas.\n- `403 Forbidden` — e-mail ainda não verificado.","operationId":"login_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Par de JWT (access + refresh) emitido para o usuário.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenPairResponse"}}}},"401":{"description":"E-mail ou senha inválidos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"E-mail ou senha inválidos."}}}},"403":{"description":"E-mail ainda não verificado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"Verifique seu e-mail antes de entrar."}}}},"400":{"description":"Requisição inválida (token de verificação/reset inválido, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}}}},"/v1/auth/refresh":{"post":{"tags":["auth"],"summary":"Renovar tokens","description":"Troca um `refresh_token` válido por um novo par (access + refresh). Use sempre que o `access_token` expirar (default: 15 minutos).\n\n**Erros:**\n- `401 Unauthorized` — refresh inválido ou expirado.\n- `403 Forbidden` — e-mail do usuário deixou de estar verificado.","operationId":"refresh_tokens_v1_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Novo par de JWT.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenPairResponse"}}}},"401":{"description":"Token de atualização inválido ou expirado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Conta sem e-mail verificado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Requisição inválida (token de verificação/reset inválido, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}}}},"/v1/auth/forgot-password":{"post":{"tags":["auth"],"summary":"Solicitar redefinição de senha","description":"Envia um e-mail com link/token para redefinir a senha. **Sempre retorna a mesma mensagem genérica**, independentemente de o e-mail existir ou não, para evitar enumeração de contas.\n\n**Comportamento por ambiente:**\n- `development`: o token é devolvido em `reset_token` na resposta.\n- `production`: token apenas no e-mail.\n\n**Validade do token:** definida por `PASSWORD_RESET_EXPIRE_MINUTES` (60 min por padrão). Apenas o token mais recente fica válido — solicitar de novo invalida o anterior.","operationId":"forgot_password_v1_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Mensagem genérica + token (apenas em desenvolvimento).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordResponse"}}}},"400":{"description":"Requisição inválida (token de verificação/reset inválido, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}}}},"/v1/auth/reset-password":{"post":{"tags":["auth"],"summary":"Aplicar nova senha","description":"Troca a senha do usuário usando o token recebido em `POST /v1/auth/forgot-password`. O token é **single-use** — após o sucesso, ele é marcado como usado e não pode ser reutilizado.\n\n**Erros:**\n- `400 Bad Request` — token inválido, expirado ou já utilizado.","operationId":"reset_password_v1_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Mensagem confirmando a redefinição.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"description":"Requisição inválida (token de verificação/reset inválido, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}}}},"/v1/users/me":{"get":{"tags":["users"],"summary":"Obter perfil do usuário autenticado","description":"Retorna os dados do usuário associado ao Bearer Token enviado.\n\nUse este endpoint logo após o login para popular o estado da aplicação com nome, CPF/CNPJ e data de nascimento.","operationId":"get_me_v1_users_me_get","responses":{"200":{"description":"Perfil completo do usuário.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["users"],"summary":"Atualizar perfil parcial","description":"Atualiza apenas os campos enviados no body (PATCH). Campos omitidos permanecem como estão. Para limpar `full_name`, envie string vazia (será convertida em `null`).\n\n**Validações:**\n- `tax_id` aceita CPF ou CNPJ com ou sem máscara — é sempre normalizado e validado pelos dígitos verificadores.\n- `full_name` é trimado e limitado a 255 caracteres.","operationId":"patch_me_v1_users_me_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileUpdate"}}},"required":true},"responses":{"200":{"description":"Perfil atualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/users/me/change-password":{"post":{"tags":["users"],"summary":"Trocar senha do usuário autenticado","description":"Troca a senha exigindo a **senha atual** como confirmação. Diferente do fluxo de `forgot-password`, este endpoint não exige token de e-mail.\n\n**Erros:**\n- `401 Unauthorized` — senha atual incorreta ou token inválido.","operationId":"change_password_v1_users_me_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"204":{"description":"Senha alterada com sucesso (sem corpo)."},"401":{"description":"Senha atual incorreta ou token inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"wrong_password":{"summary":"Senha atual incorreta","value":{"detail":"Senha atual incorreta."}},"invalid_token":{"summary":"Bearer ausente/inválido","value":{"detail":"Credenciais ausentes."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/accounts":{"get":{"tags":["financial"],"summary":"Listar contas do usuário","description":"Retorna todas as contas do usuário (inclusive arquivadas), ordenadas por ID. Use `is_archived` para diferenciar ativas de arquivadas no client.","operationId":"list_accounts_v1_accounts_get","responses":{"200":{"description":"Lista de contas.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AccountResponse"},"type":"array","title":"Response List Accounts V1 Accounts Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["financial"],"summary":"Criar conta","description":"Cria uma conta bancária ou carteira para o usuário autenticado. Use `pluggy_item_id` quando a conta vier do Open Finance.","operationId":"create_account_v1_accounts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreateRequest"}}},"required":true},"responses":{"201":{"description":"Conta recém-criada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/accounts/{account_id}":{"get":{"tags":["financial"],"summary":"Detalhar conta","description":"Retorna uma conta específica do usuário pelo ID.","operationId":"get_account_v1_accounts__account_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"integer","title":"Account Id"}}],"responses":{"200":{"description":"Detalhes da conta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["financial"],"summary":"Atualizar conta","description":"Atualiza apenas os campos enviados. Para os campos `institution_code` e `initial_balance`, enviar `null` explicitamente **limpa** o valor — demais campos com `null` são ignorados (PUT parcial).","operationId":"update_account_v1_accounts__account_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"integer","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountUpdateRequest"}}}},"responses":{"200":{"description":"Conta atualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"delete":{"tags":["financial"],"summary":"Remover ou arquivar conta","description":"Comportamento condicional para preservar histórico:\n- Se a conta **não tem** transações vinculadas: é **deletada**.\n- Se a conta **tem** transações: é **arquivada** (`is_archived=true`) para manter integridade do histórico financeiro.","operationId":"delete_account_v1_accounts__account_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"integer","title":"Account Id"}}],"responses":{"200":{"description":"Mensagem indicando se a conta foi removida ou arquivada.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Account V1 Accounts  Account Id  Delete"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/categories":{"get":{"tags":["financial"],"summary":"Listar categorias","description":"Retorna todas as categorias do usuário, ordenadas por `sort_order` e `id`. Não há paginação — categorias tendem a ser poucas dezenas.","operationId":"list_categories_v1_categories_get","responses":{"200":{"description":"Lista de categorias.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CategoryResponse"},"type":"array","title":"Response List Categories V1 Categories Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["financial"],"summary":"Criar categoria","description":"Cria uma categoria do usuário. Pode ser hierárquica via `parent_id` (deve referenciar outra categoria do mesmo usuário). Não permite duplicar `(name, kind)` para o mesmo usuário.","operationId":"create_category_v1_categories_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryCreateRequest"}}},"required":true},"responses":{"201":{"description":"Categoria criada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"409":{"description":"Conflito de unicidade ou referência em uso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"E-mail já cadastrado."}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/categories/{category_id}":{"get":{"tags":["financial"],"summary":"Detalhar categoria","description":"Retorna uma categoria específica do usuário.","operationId":"get_category_v1_categories__category_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"integer","title":"Category Id"}}],"responses":{"200":{"description":"Detalhes da categoria.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["financial"],"summary":"Atualizar categoria","description":"Atualiza apenas campos enviados. Validações:\n- `parent_id` não pode formar ciclo (referência circular).\n- A combinação `(name, kind)` não pode duplicar outra categoria do usuário.","operationId":"update_category_v1_categories__category_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"integer","title":"Category Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryUpdateRequest"}}}},"responses":{"200":{"description":"Categoria atualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"409":{"description":"Conflito de unicidade ou referência em uso.","content":{"application/json":{"example":{"detail":"E-mail já cadastrado."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["financial"],"summary":"Remover categoria","description":"Remove uma categoria. **Falha com 409** se houver transações vinculadas — desvincule ou recategorize antes de deletar.","operationId":"delete_category_v1_categories__category_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"integer","title":"Category Id"}}],"responses":{"204":{"description":"Categoria removida (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"409":{"description":"Conflito de unicidade ou referência em uso.","content":{"application/json":{"example":{"detail":"E-mail já cadastrado."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/transactions":{"post":{"tags":["financial"],"summary":"Criar lançamento","description":"Cria um lançamento financeiro. Regras por `type`:\n\n| `type`           | Campos obrigatórios além dos comuns                     |\n|------------------|---------------------------------------------------------|\n| `expense`        | nenhum (apenas `account_id`).                           |\n| `income`         | nenhum.                                                 |\n| `transfer`       | `transfer_account_id` (conta destino, ≠ `account_id`).  |\n| `card_expense`   | `credit_card_id`.                                       |\n| `card_payment`   | `credit_card_id`.                                       |\n\n**Parcelas:** envie `installment_count` e `installment_index` para marcar a transação como uma parcela. O `installment_group` é gerado automaticamente.","operationId":"create_transaction_v1_transactions_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionCreateRequest"}}}},"responses":{"201":{"description":"Lançamento criado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"get":{"tags":["financial"],"summary":"Listar lançamentos com filtros","description":"Lista lançamentos do usuário, ordenados por data desc + ID desc. Todos os filtros são opcionais e combináveis (AND).\n\nExemplos:\n- Todas as despesas do mês: `?type=expense&start_date=2026-05-01&end_date=2026-05-31`\n- Apenas de uma conta: `?account_id=1`\n- Apenas de um cartão: `?credit_card_id=3`\n- Busca textual na descrição (case-insensitive `LIKE %x%`): `?search=ifood`","operationId":"list_transactions_v1_transactions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}},{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"}},{"name":"category_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Category Id"}},{"name":"credit_card_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Card Id"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Lista de lançamentos filtrados.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TransactionResponse"},"title":"Response List Transactions V1 Transactions Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/transactions/{transaction_id}":{"get":{"tags":["financial"],"summary":"Detalhar lançamento","description":"Retorna um lançamento específico do usuário.","operationId":"get_transaction_v1_transactions__transaction_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"transaction_id","in":"path","required":true,"schema":{"type":"integer","title":"Transaction Id"}}],"responses":{"200":{"description":"Detalhes do lançamento.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["financial"],"summary":"Atualizar lançamento","description":"Atualiza apenas campos enviados. Trocar o `type` mantém a integridade das relações (transfer ↔ card ↔ conta) — campos incompatíveis são automaticamente limpos.","operationId":"update_transaction_v1_transactions__transaction_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"transaction_id","in":"path","required":true,"schema":{"type":"integer","title":"Transaction Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionUpdateRequest"}}}},"responses":{"200":{"description":"Lançamento atualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"delete":{"tags":["financial"],"summary":"Remover lançamento (com opção de remover grupo de parcelas)","description":"Remove um lançamento.\n\nUse `?delete_group=true` para também remover **todas as parcelas** do mesmo `installment_group` (apenas se o lançamento tiver grupo). Útil para excluir uma compra parcelada por inteiro de uma só vez.","operationId":"delete_transaction_v1_transactions__transaction_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"transaction_id","in":"path","required":true,"schema":{"type":"integer","title":"Transaction Id"}},{"name":"delete_group","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Delete Group"}}],"responses":{"204":{"description":"Lançamento(s) removido(s) (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/classification-rules":{"get":{"tags":["financial"],"summary":"Listar regras de classificação","description":"Lista regras ordenadas por `priority` asc + `id` asc.","operationId":"list_classification_rules_v1_classification_rules_get","responses":{"200":{"description":"Lista de regras.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ClassificationRuleResponse"},"type":"array","title":"Response List Classification Rules V1 Classification Rules Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["financial"],"summary":"Criar regra de classificação automática","description":"Cria uma regra `keyword → category_id`. Sempre que um lançamento contiver `keyword` na descrição (case-insensitive), a categoria é aplicada automaticamente.\n\n**Prioridade:** quanto menor o número, maior a prioridade. Regras com prioridade igual seguem ordem de criação. Não permite duplicar `keyword` para o mesmo usuário.","operationId":"create_classification_rule_v1_classification_rules_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassificationRuleCreateRequest"}}},"required":true},"responses":{"201":{"description":"Regra criada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassificationRuleResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"409":{"description":"Conflito de unicidade ou referência em uso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"E-mail já cadastrado."}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/classification-rules/{rule_id}":{"get":{"tags":["financial"],"summary":"Detalhar regra de classificação","description":"Retorna uma regra específica do usuário.","operationId":"get_classification_rule_v1_classification_rules__rule_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"integer","title":"Rule Id"}}],"responses":{"200":{"description":"Detalhes da regra.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassificationRuleResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["financial"],"summary":"Atualizar regra de classificação","description":"Atualiza apenas campos enviados. `keyword` continua único por usuário (case-insensitive).","operationId":"update_classification_rule_v1_classification_rules__rule_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"integer","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassificationRuleUpdateRequest"}}}},"responses":{"200":{"description":"Regra atualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassificationRuleResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"409":{"description":"Conflito de unicidade ou referência em uso.","content":{"application/json":{"example":{"detail":"E-mail já cadastrado."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["financial"],"summary":"Remover regra de classificação","description":"Remove uma regra. Lançamentos já classificados não são afetados.","operationId":"delete_classification_rule_v1_classification_rules__rule_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"integer","title":"Rule Id"}}],"responses":{"204":{"description":"Regra removida (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/health":{"get":{"tags":["health"],"summary":"Health check do serviço","description":"Endpoint **público** (não requer autenticação) usado por load balancers, monitoramento e smoke tests para verificar se a aplicação está no ar.\n\n- Sempre retorna `200 OK` quando o processo FastAPI está respondendo.\n- Não consulta banco de dados nem dependências externas — é um *liveness probe*.\n- O campo `environment` reflete a variável de ambiente `ENVIRONMENT`.","operationId":"read_health_v1_health_get","responses":{"200":{"description":"Estado atual do serviço, nome e ambiente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/cards":{"get":{"tags":["support"],"summary":"Listar cartões","description":"Lista todos os cartões do usuário, ordenados por ID.","operationId":"list_cards_v1_cards_get","responses":{"200":{"description":"Lista de cartões.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CreditCardResponse"},"type":"array","title":"Response List Cards V1 Cards Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["support"],"summary":"Criar cartão de crédito","description":"Cria um cartão de crédito com fechamento, vencimento e (opcionalmente) conta de pagamento da fatura. `theme_color` em formato `#RRGGBB`. Opcionalmente informe `pluggy_item_id` / `external_id` para vincular ao Open Finance (Pluggy).","operationId":"create_card_v1_cards_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditCardCreateRequest"}}},"required":true},"responses":{"201":{"description":"Cartão criado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditCardResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"Conta não encontrada."}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/cards/{card_id}":{"get":{"tags":["support"],"summary":"Detalhar cartão","description":"Retorna um cartão específico do usuário.","operationId":"get_card_v1_cards__card_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"card_id","in":"path","required":true,"schema":{"type":"integer","title":"Card Id"}}],"responses":{"200":{"description":"Detalhes do cartão.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditCardResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["support"],"summary":"Atualizar cartão","description":"Atualiza apenas campos enviados. `theme_color` validado contra `#RRGGBB`. Inclui `pluggy_item_id` e `external_id` para vínculo Pluggy.","operationId":"update_card_v1_cards__card_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"card_id","in":"path","required":true,"schema":{"type":"integer","title":"Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardUpdateRequest"}}}},"responses":{"200":{"description":"Cartão atualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditCardResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"delete":{"tags":["support"],"summary":"Remover cartão","description":"Remove um cartão e seus ciclos de fatura. **Falha com 409** se houver transações vinculadas — desvincule ou exclua antes.","operationId":"delete_card_v1_cards__card_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"card_id","in":"path","required":true,"schema":{"type":"integer","title":"Card Id"}}],"responses":{"204":{"description":"Cartão removido (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}},"409":{"description":"Conflito de unicidade ou referência em uso.","content":{"application/json":{"example":{"detail":"E-mail já cadastrado."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/cards/{card_id}/statement-cycle":{"get":{"tags":["support"],"summary":"Calcular ciclo de fatura para uma data","description":"Calcula a janela do **ciclo aberto** que contém `reference_date` para o cartão e devolve `period_start`, `period_end` (fechamento), `due_date` (vencimento) e o total de despesas no ciclo. Persiste/atualiza um registro `CardCycle` correspondente.\n\n**Regras:**\n- O fechamento mensal é `closing_day` ajustado ao número de dias do mês.\n- Se a data de referência **passou** do fechamento, o ciclo aberto vai até o `closing_day` do próximo mês.\n- O vencimento é no mesmo mês do fechamento se `due_day > closing_day`, senão no mês seguinte.\n- O total considera apenas `expense` e `card_expense` no período (`card_payment` é pagamento, não compõe o devido).","operationId":"get_statement_cycle_v1_cards__card_id__statement_cycle_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"card_id","in":"path","required":true,"schema":{"type":"integer","title":"Card Id"}},{"name":"reference_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Reference Date"}}],"responses":{"200":{"description":"Janela do ciclo + total devido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardStatementCycleResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/commitments":{"get":{"tags":["support"],"summary":"Listar compromissos","description":"Lista todos os compromissos do usuário, ordenados por `due_date` asc.","operationId":"list_commitments_v1_commitments_get","responses":{"200":{"description":"Lista de compromissos.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CommitmentResponse"},"type":"array","title":"Response List Commitments V1 Commitments Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["support"],"summary":"Criar compromisso (a pagar ou a receber)","description":"Cria um compromisso financeiro futuro. Compromissos `open` no horizonte de uma projeção (`GET /v1/projections`) são aplicados ao saldo previsto.","operationId":"create_commitment_v1_commitments_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentCreateRequest"}}},"required":true},"responses":{"201":{"description":"Compromisso criado (status inicial `open`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/commitments/{commitment_id}":{"get":{"tags":["support"],"summary":"Detalhar compromisso","description":"Retorna um compromisso específico do usuário.","operationId":"get_commitment_v1_commitments__commitment_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"commitment_id","in":"path","required":true,"schema":{"type":"integer","title":"Commitment Id"}}],"responses":{"200":{"description":"Detalhes do compromisso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["support"],"summary":"Atualizar compromisso","description":"Atualiza apenas campos enviados. Mude `status` para `paid` quando o compromisso for liquidado — assim ele para de afetar projeções.","operationId":"update_commitment_v1_commitments__commitment_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"commitment_id","in":"path","required":true,"schema":{"type":"integer","title":"Commitment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentUpdateRequest"}}}},"responses":{"200":{"description":"Compromisso atualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"delete":{"tags":["support"],"summary":"Remover compromisso","description":"Remove definitivamente um compromisso do usuário.","operationId":"delete_commitment_v1_commitments__commitment_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"commitment_id","in":"path","required":true,"schema":{"type":"integer","title":"Commitment Id"}}],"responses":{"204":{"description":"Compromisso removido (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/loan-or-investments":{"get":{"tags":["support"],"summary":"Listar empréstimos e investimentos","description":"Lista todos os registros do usuário, ordenados por ID.","operationId":"list_loan_or_investments_v1_loan_or_investments_get","responses":{"200":{"description":"Lista de empréstimos/investimentos.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/LoanOrInvestmentResponse"},"type":"array","title":"Response List Loan Or Investments V1 Loan Or Investments Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["support"],"summary":"Criar empréstimo ou investimento","description":"Cria um registro de **empréstimo** (`type=loan`) ou **investimento** (`type=investment`). O `balance` representa o saldo atual; atualizações futuras são manuais (não há sync de mercado nesta fase).","operationId":"create_loan_or_investment_v1_loan_or_investments_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoanOrInvestmentCreateRequest"}}},"required":true},"responses":{"201":{"description":"Registro criado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoanOrInvestmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/loan-or-investments/{item_id}":{"get":{"tags":["support"],"summary":"Detalhar empréstimo/investimento","description":"Retorna um registro específico do usuário.","operationId":"get_loan_or_investment_v1_loan_or_investments__item_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"integer","title":"Item Id"}}],"responses":{"200":{"description":"Detalhes do registro.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoanOrInvestmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["support"],"summary":"Atualizar empréstimo/investimento","description":"Atualiza apenas campos enviados. Use para refletir o saldo atual.","operationId":"update_loan_or_investment_v1_loan_or_investments__item_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"integer","title":"Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoanOrInvestmentUpdateRequest"}}}},"responses":{"200":{"description":"Registro atualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoanOrInvestmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"delete":{"tags":["support"],"summary":"Remover empréstimo/investimento","description":"Remove definitivamente o registro.","operationId":"delete_loan_or_investment_v1_loan_or_investments__item_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"integer","title":"Item Id"}}],"responses":{"204":{"description":"Registro removido (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/recurring":{"get":{"tags":["support"],"summary":"Listar templates de recorrência","description":"Lista todos os templates de compromisso recorrente do usuário. Hoje apenas frequência mensal é suportada.","operationId":"list_recurring_templates_v1_recurring_get","responses":{"200":{"description":"Lista de templates.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/RecurringTemplateResponse"},"type":"array","title":"Response List Recurring Templates V1 Recurring Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["support"],"summary":"Criar template de recorrência mensal","description":"Cria um template para gerar compromissos recorrentes. Nesta fase, apenas `kind=commitment` e `frequency=monthly` são suportados — outros valores retornam `422`.\n\n**`anchor_date`** define a primeira ocorrência; as próximas são geradas via `POST /v1/recurring/{template_id}/generate`.","operationId":"create_recurring_template_v1_recurring_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateCreateRequest"}}},"required":true},"responses":{"201":{"description":"Template criado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/recurring/{template_id}":{"get":{"tags":["support"],"summary":"Detalhar template de recorrência","description":"Retorna um template específico do usuário.","operationId":"get_recurring_template_v1_recurring__template_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}}],"responses":{"200":{"description":"Detalhes do template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"put":{"tags":["support"],"summary":"Atualizar template de recorrência","description":"Atualiza apenas campos enviados. Use `is_active=false` para pausar a recorrência sem perder o histórico.","operationId":"update_recurring_template_v1_recurring__template_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateUpdateRequest"}}}},"responses":{"200":{"description":"Template atualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}},"delete":{"tags":["support"],"summary":"Encerrar e remover template de recorrência","description":"Encerra a recorrência de forma segura para o histórico:\n\n- **Remove** compromissos **abertos** (`status=open`) com `due_date` **≥ hoje** (mês atual e futuros).\n- **Mantém** compromissos com vencimento **anterior a hoje** e desvincula do template (`recurring_template_id` nulo).\n- **Desvincula** lançamentos (`transactions`) que apontavam para este template.\n- Por fim **remove** o registro do template.","operationId":"delete_recurring_template_v1_recurring__template_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}}],"responses":{"204":{"description":"Template removido (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/recurring/{template_id}/generate":{"post":{"tags":["support"],"summary":"Gerar próxima ocorrência da recorrência","description":"Cria a próxima ocorrência de um template de recorrência cuja data seja **≥ `reference_date`**. A data é calculada a partir de `anchor_date` somando `interval` meses até atingir `reference_date`. Útil para um cron job mensal.","operationId":"generate_recurring_occurrence_v1_recurring__template_id__generate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRecurringRequest"}}}},"responses":{"201":{"description":"Compromisso gerado a partir do template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/commitments/{commitment_id}/pay":{"post":{"tags":["support"],"summary":"Baixar compromisso","description":"Marca compromisso como `paid` e opcionalmente vincula transação existente (`transaction_id`) ou cria um lançamento manual quando `create_transaction=true`.","operationId":"pay_commitment_v1_commitments__commitment_id__pay_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"commitment_id","in":"path","required":true,"schema":{"type":"integer","title":"Commitment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentPayRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/reconciliation/commitment-suggestions":{"get":{"tags":["support"],"summary":"Sugerir conciliação compromisso x Pluggy","description":"Sugere matches entre compromissos `open` e transações com `source=pluggy` usando score por valor/data/descrição. Retorna apenas score >= 0.5.","operationId":"get_commitment_suggestions_v1_reconciliation_commitment_suggestions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CommitmentSuggestionResponse"},"type":"array","title":"Response Get Commitment Suggestions V1 Reconciliation Commitment Suggestions Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/reconciliation/commitment-matches":{"post":{"tags":["support"],"summary":"Confirmar match compromisso","description":"Confirma vínculo compromisso x transação Pluggy: marca compromisso como `paid` e grava `matched_transaction_id`.","operationId":"create_commitment_match_v1_reconciliation_commitment_matches_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentMatchCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitmentResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"Conta não encontrada."}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/reports/summary":{"get":{"tags":["reports"],"summary":"Resumo financeiro do período","description":"Calcula totais de **receitas**, **despesas**, **saldo líquido** e a distribuição de despesas por categoria no intervalo `[start_date, end_date]` (inclusivo).\n\n**Tipos considerados:**\n- Despesas: `expense`, `card_expense`.\n- Receitas: `income`.\n\nUse `?account_id=N` para restringir a uma conta específica.","operationId":"get_report_summary_v1_reports_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Start Date"}},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"End Date"}},{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"}}],"responses":{"200":{"description":"Totais agregados + distribuição por categoria.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportSummaryResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/reports/by-period":{"get":{"tags":["reports"],"summary":"Série temporal de receitas × despesas","description":"Agrega receitas e despesas por **período** (mensal ou diário) dentro do intervalo informado. Útil para gráficos de linha/coluna no app.\n\n**Parâmetros:**\n- `period=monthly` (default): agrupa por `YYYY-MM`.\n- `period=daily`: agrupa por `YYYY-MM-DD`.\n- `account_id` opcional: restringe a uma conta.\n\nResultado ordenado crescente por período.","operationId":"get_report_by_period_v1_reports_by_period_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Start Date"}},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"End Date"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"monthly","title":"Period"}},{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"}}],"responses":{"200":{"description":"Série temporal ordenada.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PeriodSummary"},"title":"Response Get Report By Period V1 Reports By Period Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/projections":{"get":{"tags":["reports"],"summary":"Projeção de saldo futuro","description":"Projeta o saldo da conta para os próximos `horizon_days` (1..365), começando do saldo atual (calculado das transações históricas) e aplicando os **compromissos abertos** (`commitments` com `status=open`) que vencem no horizonte.\n\n**Pontos da série:**\n- Sempre inclui o dia atual e o último do horizonte.\n- Pontos intermediários: dia 1 de cada mês e qualquer data com evento.\n\n**Erros:**\n- `404` — conta não encontrada / não pertence ao usuário.\n- `422` — `horizon_days` fora de `[1, 365]`.","operationId":"get_projections_v1_projections_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"query","required":true,"schema":{"type":"integer","title":"Account Id"}},{"name":"horizon_days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Horizon Days"}}],"responses":{"200":{"description":"Saldo projetado dia a dia (com pontos relevantes).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectionResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"example":{"detail":"Conta não encontrada."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/pluggy/credentials":{"get":{"tags":["pluggy"],"summary":"Obter credenciais Pluggy do usuário","description":"Retorna as credenciais Pluggy salvas (descriptografadas para que o app exiba ofuscadas), o estado da última sync e os `item_ids` vinculados.","operationId":"get_pluggy_credentials_v1_pluggy_credentials_get","responses":{"200":{"description":"Credenciais e estado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluggyCredentialsResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"Conta não encontrada."}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["pluggy"],"summary":"Salvar credenciais Pluggy (BYOK)","description":"Salva o par `clientId` / `clientSecret` da conta Pluggy do usuário. Os valores são **criptografados** com Fernet usando `PLUGGY_ENCRYPTION_KEY`. Se já existirem credenciais, são **substituídas**.\n\n**Pré-requisito de produção:** `PLUGGY_ENCRYPTION_KEY` precisa ser uma chave Fernet válida (32 bytes URL-safe base64). Em `development`, uma chave estável por processo é gerada automaticamente.","operationId":"save_pluggy_credentials_v1_pluggy_credentials_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluggyCredentialsCreateRequest"}}},"required":true},"responses":{"201":{"description":"Credenciais salvas e item_ids descobertos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluggyCredentialsResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["pluggy"],"summary":"Remover credenciais Pluggy","description":"Remove as credenciais Pluggy do usuário. **Não desconecta os Items na Pluggy** — para isso, use o console Pluggy. Contas e cartões já importados continuam no app.","operationId":"delete_pluggy_credentials_v1_pluggy_credentials_delete","responses":{"204":{"description":"Credenciais removidas (sem corpo)."},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/pluggy/sync":{"post":{"tags":["pluggy"],"summary":"Sincronizar contas e transações da Pluggy","description":"Importa contas (BANK → `Account`, CREDIT → `CreditCard`) e transações dos `itemIds` informados. Se nenhum for enviado, usa os `pluggy_item_id` já vinculados no banco.\n\n**Mapeamento Pluggy → domínio:**\n- `type=BANK` → `Account`. `external_id` = ID da conta Pluggy, `initial_balance` = `balance`, `currency` = `currencyCode`.\n- `type=CREDIT` → `CreditCard`. `limit_amount` = `creditLimit` explícito ou |balance| + `availableCredit`. `due_day` de `billDueDate`/`balanceDueDate`. `closing_day` de `balanceCloseDate` ou heurística (`due_day - 7`).\n- Transações `DEBIT` → `expense`; demais → `income`. `amount` sempre positivo. Datetimes UTC convertidos para BRT (UTC−3).\n- Parcelas vêm de `creditCardMetadata.totalInstallments` / `installmentNumber`.\n- Transações `status=PENDING` em cartão são **ignoradas** (parcela futura / fatura aberta).\n\n**Idempotência:** transações já importadas (`external_id` igual) não são duplicadas.\n\n**Possíveis status na resposta:**\n- `success`: tudo importado, sem erros.\n- `partial`: parte importada, parte falhou.\n- `failed`: nada importado (credenciais ausentes, autenticação Pluggy falhou, sem `itemIds`).","operationId":"sync_pluggy_data_v1_pluggy_sync_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/PluggySyncRequest"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Contagens de importação e mensagem de erro agregada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluggySyncResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/pluggy/webhook":{"post":{"tags":["pluggy"],"summary":"Receber webhook da Pluggy (público)","description":"Endpoint **público** chamado pela Pluggy para notificar eventos. Persiste o evento e processa em background com idempotência e retry (até 3 tentativas com backoff).\n\n**Autenticação opcional** (configurada via `PLUGGY_WEBHOOK_SECRET`):\n1. **Bearer token:** header `Authorization: Bearer <secret>` ou exatamente `<secret>`.\n2. **HMAC-SHA256:** header `X-Pluggy-Signature` (ou `X-Webhook-Signature`, `X-Hub-Signature-256`, `X-Signature`) com o hex puro ou prefixado por `sha256=`.\n\nSem `PLUGGY_WEBHOOK_SECRET` configurado, todo webhook é aceito (não recomendado em produção).\n\n**Idempotência:** chave construída a partir de `eventId` (preferencial) ou hash do corpo. Eventos duplicados retornam `200` com `duplicate=true` e não reexecutam o processamento.\n\n**Eventos suportados:**\n- `item/created`, `item/updated`, `item/login_succeeded`, `transactions/created`, `transactions/updated` → dispara sync.\n- `item/waiting_user_input` → marca credenciais como `sync_failed` (MFA).\n- `item/error` → marca erro nas credenciais.\n- `item/deleted` → desvincula `pluggy_item_id` das contas/cartões.","operationId":"receive_pluggy_webhook_v1_pluggy_webhook_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload"}}}},"responses":{"202":{"description":"Evento recebido e enfileirado para processamento.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Receive Pluggy Webhook V1 Pluggy Webhook Post"}}}},"400":{"description":"Payload inválido (sem campo `event`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Webhook não autorizado (assinatura/Bearer inválido).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/reconciliation/suggestions":{"get":{"tags":["pluggy","pluggy"],"summary":"Sugestões de conciliação manual × importado","description":"Retorna sugestões automáticas de match entre lançamentos **manuais** (criados pelo usuário) e **importados** da Pluggy (`source=pluggy`).\n\n**Como o score é calculado** (somado, máximo `1.0`):\n- Mesmo valor (diff < 0.01): +0.4. Valores próximos (diff < 1.0): +0.2.\n- Mesma data: +0.3. Datas a até 2 dias: +0.15.\n- Descrição contendo a outra (case-insensitive): +0.3.\n- Overlap de palavras: +0.1.\n\nApenas pares com **score ≥ 0.5** são retornados, ordenados desc por score. Pares já confirmados via `POST /reconciliation/matches` ou rejeitados via `POST /reconciliation/rejections` são excluídos.\n\nUse `?account_id=N` para restringir a conta específica.","operationId":"get_reconciliation_suggestions_v1_reconciliation_suggestions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"}}],"responses":{"200":{"description":"Lista ordenada de sugestões (mais confiáveis primeiro).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ReconciliationSuggestion"},"title":"Response Get Reconciliation Suggestions V1 Reconciliation Suggestions Get"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]},"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"}}}}}}},"/v1/reconciliation/matches":{"post":{"tags":["pluggy","pluggy"],"summary":"Confirmar match de conciliação","description":"Confirma que um lançamento **manual** corresponde a um lançamento **importado** da Pluggy. Após o match, ambos são considerados conciliados e param de aparecer em `GET /reconciliation/suggestions`.\n\n**Validação de papéis:**\n- A primeira transação deve ser manual (`source` ≠ `pluggy`).\n- A segunda deve ser da Pluggy (`source = pluggy`).","operationId":"create_match_v1_reconciliation_matches_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchCreateRequest"}}},"required":true},"responses":{"201":{"description":"Match confirmado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"Conta não encontrada."}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/reconciliation/rejections":{"post":{"tags":["pluggy","pluggy"],"summary":"Rejeitar sugestão de conciliação","description":"Marca um par (manual, importado) como rejeitado para que **não seja sugerido novamente**. Também atualiza um match existente para `status=rejected` se houver.","operationId":"create_rejection_v1_reconciliation_rejections_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectionCreateRequest"}}},"required":true},"responses":{"201":{"description":"Rejeição registrada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectionResponse"}}}},"401":{"description":"Token ausente, expirado ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"Sem header Authorization","value":{"detail":"Credenciais ausentes."}},"expired":{"summary":"Token expirado","value":{"detail":"Token expirado."}},"invalid":{"summary":"Token malformado","value":{"detail":"Token inválido."}}}}}},"404":{"description":"Recurso inexistente ou não pertence ao usuário do token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"detail":"Conta não encontrada."}}}},"422":{"description":"Falha de validação no payload (Pydantic) ou regra de domínio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorResponse"},"example":{"detail":[{"loc":["body","amount"],"msg":"Input should be greater than 0","type":"greater_than"}]}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"AccountCreateRequest":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name","description":"Nome amigável da conta."},"type":{"type":"string","maxLength":40,"title":"Type","description":"Tipo livre (`checking`, `savings`, `wallet`, `investment`, ...).","default":"checking"},"currency":{"type":"string","maxLength":8,"minLength":3,"title":"Currency","description":"Código ISO 4217 (3 letras).","default":"BRL"},"is_archived":{"type":"boolean","title":"Is Archived","description":"Conta arquivada não aparece nos KPIs.","default":false},"institution_code":{"type":"string","maxLength":10,"minLength":1,"title":"Institution Code","description":"Código da instituição (ex.: `NUBANK`, `ITAU`, `OUTRO`). Usado para escolher o ícone no app."},"pluggy_item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pluggy Item Id","description":"UUID do Item Pluggy quando a conta vier do Open Finance."},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"ID externo (ex.: ID da conta na Pluggy)."},"initial_balance":{"type":"number","title":"Initial Balance","description":"Saldo inicial da conta (positivo ou negativo)."}},"type":"object","required":["name","institution_code","initial_balance"],"title":"AccountCreateRequest","description":"Cria uma conta bancária ou carteira do usuário.","examples":[{"currency":"BRL","initial_balance":1500.0,"institution_code":"NUBANK","is_archived":false,"name":"Conta Corrente Nubank","type":"checking"}]},"AccountResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"type":{"type":"string","title":"Type"},"currency":{"type":"string","title":"Currency"},"is_archived":{"type":"boolean","title":"Is Archived"},"institution_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Institution Code"},"pluggy_item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pluggy Item Id"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"initial_balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Initial Balance"}},"type":"object","required":["id","name","type","currency","is_archived","institution_code","pluggy_item_id","external_id","initial_balance"],"title":"AccountResponse","examples":[{"currency":"BRL","id":1,"initial_balance":1500.0,"institution_code":"NUBANK","is_archived":false,"name":"Conta Corrente Nubank","type":"checking"}]},"AccountUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Name"},"type":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"Type"},"currency":{"anyOf":[{"type":"string","maxLength":8,"minLength":3},{"type":"null"}],"title":"Currency"},"institution_code":{"anyOf":[{"type":"string","maxLength":10},{"type":"null"}],"title":"Institution Code"},"initial_balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Initial Balance"}},"type":"object","title":"AccountUpdateRequest","description":"Patch parcial de conta. Para limpar `institution_code`/`initial_balance`, envie `null`."},"CardStatementCycleResponse":{"properties":{"credit_card_id":{"type":"integer","title":"Credit Card Id"},"period_start":{"type":"string","format":"date","title":"Period Start","description":"Início do ciclo aberto contendo a data de referência."},"period_end":{"type":"string","format":"date","title":"Period End","description":"Fechamento do ciclo (dia `closing_day`)."},"due_date":{"type":"string","format":"date","title":"Due Date","description":"Vencimento da fatura."},"total_amount":{"type":"number","title":"Total Amount","description":"Soma de `expense`/`card_expense` no ciclo."},"status":{"type":"string","title":"Status","description":"`closed` quando o ciclo está calculado."}},"type":"object","required":["credit_card_id","period_start","period_end","due_date","total_amount","status"],"title":"CardStatementCycleResponse","description":"Janela de fatura (ciclo) calculada para uma data de referência.","examples":[{"credit_card_id":3,"due_date":"2026-06-05","period_end":"2026-05-25","period_start":"2026-04-26","status":"closed","total_amount":1234.5}]},"CardUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"limit_amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Limit Amount"},"closing_day":{"anyOf":[{"type":"integer","maximum":31.0,"minimum":1.0},{"type":"null"}],"title":"Closing Day"},"due_day":{"anyOf":[{"type":"integer","maximum":31.0,"minimum":1.0},{"type":"null"}],"title":"Due Day"},"payment_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payment Account Id"},"brand":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Brand"},"theme_color":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Theme Color"},"pluggy_item_id":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Pluggy Item Id"},"external_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"External Id"}},"type":"object","title":"CardUpdateRequest"},"CategoryCreateRequest":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"kind":{"type":"string","enum":["income","expense"],"title":"Kind","description":"`income` para receitas, `expense` para despesas.","default":"expense"},"parent_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent Id","description":"ID de outra categoria do mesmo usuário (cria hierarquia)."},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon","description":"Identificador do ícone no app."},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Cor em formato `#RRGGBB`."},"sort_order":{"type":"integer","title":"Sort Order","description":"Ordem de exibição.","default":0},"is_active":{"type":"boolean","title":"Is Active","description":"Se false, a categoria não aparece em novos lançamentos (histórico preservado).","default":true}},"type":"object","required":["name"],"title":"CategoryCreateRequest","description":"Cria uma categoria (com hierarquia opcional via `parent_id`).","examples":[{"color":"#F97316","icon":"restaurant","is_active":true,"kind":"expense","name":"Alimentação","sort_order":10}]},"CategoryResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"kind":{"type":"string","title":"Kind"},"parent_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent Id"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"sort_order":{"type":"integer","title":"Sort Order"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","name","kind","parent_id","icon","color","sort_order","is_active"],"title":"CategoryResponse","examples":[{"color":"#F97316","icon":"restaurant","id":7,"is_active":true,"kind":"expense","name":"Alimentação","sort_order":10}]},"CategorySummary":{"properties":{"category_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Category Id","description":"ID da categoria; `null` para 'sem categoria'."},"category_name":{"type":"string","title":"Category Name"},"amount":{"type":"number","title":"Amount","description":"Total gasto na categoria no período."},"percentage":{"type":"number","title":"Percentage","description":"Participação no total de despesas (%) com 2 casas decimais."}},"type":"object","required":["category_id","category_name","amount","percentage"],"title":"CategorySummary","description":"Linha do resumo de despesas agregadas por categoria.","examples":[{"amount":845.3,"category_id":7,"category_name":"Alimentação","percentage":32.55}]},"CategoryUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"kind":{"anyOf":[{"type":"string","enum":["income","expense"]},{"type":"null"}],"title":"Kind"},"parent_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent Id"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"sort_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sort Order"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"CategoryUpdateRequest"},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","maxLength":128,"minLength":1,"title":"Current Password","description":"Senha atual do usuário."},"new_password":{"type":"string","maxLength":128,"minLength":8,"title":"New Password","description":"Nova senha (8 a 128 caracteres)."}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest","description":"Troca de senha — requer a senha atual para confirmar.","examples":[{"current_password":"SenhaAtual123!","new_password":"NovaSenhaForte456!"}]},"ClassificationRuleCreateRequest":{"properties":{"keyword":{"type":"string","maxLength":120,"minLength":1,"title":"Keyword","description":"Trecho da descrição (case-insensitive)."},"category_id":{"type":"integer","minimum":1.0,"title":"Category Id","description":"Categoria a aplicar."},"priority":{"type":"integer","maximum":9999.0,"minimum":1.0,"title":"Priority","description":"Quanto menor, maior a prioridade (regras ordenadas crescente).","default":1}},"type":"object","required":["keyword","category_id"],"title":"ClassificationRuleCreateRequest","description":"Regra: se `keyword` estiver na descrição da transação, aplicar `category_id`.","examples":[{"category_id":7,"keyword":"ifood","priority":10}]},"ClassificationRuleResponse":{"properties":{"id":{"type":"integer","title":"Id"},"keyword":{"type":"string","title":"Keyword"},"category_id":{"type":"integer","title":"Category Id"},"priority":{"type":"integer","title":"Priority"}},"type":"object","required":["id","keyword","category_id","priority"],"title":"ClassificationRuleResponse","examples":[{"category_id":7,"id":5,"keyword":"ifood","priority":10}]},"ClassificationRuleUpdateRequest":{"properties":{"keyword":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Keyword"},"category_id":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Category Id"},"priority":{"anyOf":[{"type":"integer","maximum":9999.0,"minimum":1.0},{"type":"null"}],"title":"Priority"}},"type":"object","title":"ClassificationRuleUpdateRequest"},"CommitmentCreateRequest":{"properties":{"type":{"type":"string","enum":["payable","receivable"],"title":"Type"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Amount"},"due_date":{"type":"string","format":"date","title":"Due Date"},"description":{"type":"string","maxLength":255,"minLength":1,"title":"Description"},"reminder_days_before":{"anyOf":[{"type":"integer","maximum":60.0,"minimum":0.0},{"type":"null"}],"title":"Reminder Days Before","description":"Dispara lembrete N dias antes do vencimento (0..60)."},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id","description":"Conta prevista para liquidação do compromisso."}},"type":"object","required":["type","amount","due_date","description"],"title":"CommitmentCreateRequest","description":"Compromisso a pagar (`payable`) ou a receber (`receivable`).","examples":[{"amount":"1500.00","description":"Aluguel","due_date":"2026-05-15","reminder_days_before":3,"type":"payable"}]},"CommitmentMatchCreateRequest":{"properties":{"commitment_id":{"type":"integer","title":"Commitment Id"},"transaction_id":{"type":"integer","title":"Transaction Id"}},"type":"object","required":["commitment_id","transaction_id"],"title":"CommitmentMatchCreateRequest"},"CommitmentPayRequest":{"properties":{"transaction_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Transaction Id"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount"},"transaction_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Transaction Date"},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"},"create_transaction":{"type":"boolean","title":"Create Transaction","default":false}},"type":"object","title":"CommitmentPayRequest"},"CommitmentResponse":{"properties":{"id":{"type":"integer","title":"Id"},"type":{"type":"string","title":"Type"},"amount":{"type":"number","title":"Amount"},"due_date":{"type":"string","format":"date","title":"Due Date"},"description":{"type":"string","title":"Description"},"status":{"type":"string","title":"Status","description":"`open`, `paid`, `cancelled`, `overdue`."},"reminder_days_before":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reminder Days Before"},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"},"matched_transaction_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Matched Transaction Id"},"recurring_template_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recurring Template Id"}},"type":"object","required":["id","type","amount","due_date","description","status","reminder_days_before","account_id","matched_transaction_id","recurring_template_id"],"title":"CommitmentResponse"},"CommitmentSuggestionResponse":{"properties":{"commitment_id":{"type":"integer","title":"Commitment Id"},"transaction_id":{"type":"integer","title":"Transaction Id"},"score":{"type":"number","title":"Score"},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons"}},"type":"object","required":["commitment_id","transaction_id","score","reasons"],"title":"CommitmentSuggestionResponse"},"CommitmentUpdateRequest":{"properties":{"type":{"anyOf":[{"type":"string","enum":["payable","receivable"]},{"type":"null"}],"title":"Type"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount"},"due_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Due Date"},"description":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Description"},"status":{"anyOf":[{"type":"string","enum":["open","paid","cancelled","overdue"]},{"type":"null"}],"title":"Status"},"reminder_days_before":{"anyOf":[{"type":"integer","maximum":60.0,"minimum":0.0},{"type":"null"}],"title":"Reminder Days Before"},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"},"matched_transaction_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Matched Transaction Id"}},"type":"object","title":"CommitmentUpdateRequest"},"CreditCardCreateRequest":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"limit_amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Limit Amount","description":"Limite total do cartão."},"closing_day":{"type":"integer","maximum":31.0,"minimum":1.0,"title":"Closing Day","description":"Dia de fechamento da fatura (1..31)."},"due_day":{"type":"integer","maximum":31.0,"minimum":1.0,"title":"Due Day","description":"Dia de vencimento da fatura (1..31)."},"payment_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payment Account Id","description":"Conta usada para pagar a fatura. Pode ser definida depois."},"brand":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Brand","description":"Bandeira (`VISA`, `MASTERCARD`, ...)."},"theme_color":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Theme Color","description":"Cor do cartão no app, em `#RRGGBB`."},"pluggy_item_id":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Pluggy Item Id","description":"UUID do Item Pluggy (Open Finance), para sync e webhooks."},"external_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"External Id","description":"ID externo da conta cartão na Pluggy (conta `CREDIT`)."}},"type":"object","required":["name","limit_amount","closing_day","due_day"],"title":"CreditCardCreateRequest","description":"Cria um cartão de crédito do usuário.","examples":[{"brand":"MASTERCARD","closing_day":25,"due_day":5,"limit_amount":"5000.00","name":"Nubank Roxinho","payment_account_id":1,"theme_color":"#820AD1"}]},"CreditCardResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"limit_amount":{"type":"number","title":"Limit Amount"},"closing_day":{"type":"integer","title":"Closing Day"},"due_day":{"type":"integer","title":"Due Day"},"payment_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payment Account Id"},"pluggy_item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pluggy Item Id"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"theme_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Theme Color"}},"type":"object","required":["id","name","limit_amount","closing_day","due_day","payment_account_id","brand","theme_color"],"title":"CreditCardResponse","examples":[{"brand":"MASTERCARD","closing_day":25,"due_day":5,"id":3,"limit_amount":5000.0,"name":"Nubank Roxinho","payment_account_id":1,"theme_color":"#820AD1"}]},"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Mensagem em pt-BR descrevendo o problema.","examples":["E-mail ou senha inválidos."]}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"Formato padrão de erro retornado pela API.","examples":[{"detail":"Recurso não encontrado."}]},"ForgotPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPasswordRequest","description":"Solicita o envio do e-mail de redefinição de senha.","examples":[{"email":"joao.silva@example.com"}]},"ForgotPasswordResponse":{"properties":{"message":{"type":"string","title":"Message","description":"Mensagem genérica para não vazar quais e-mails existem na base. Sempre é a mesma, exista o usuário ou não."},"reset_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reset Token","description":"**Apenas em ambientes não-produção** para facilitar testes."}},"type":"object","required":["message"],"title":"ForgotPasswordResponse","examples":[{"message":"Se existir uma conta para este e-mail, enviaremos instruções para redefinir a senha."}]},"GenerateRecurringRequest":{"properties":{"reference_date":{"type":"string","format":"date","title":"Reference Date"}},"type":"object","required":["reference_date"],"title":"GenerateRecurringRequest","description":"Solicita geração da próxima ocorrência ≥ `reference_date`.","examples":[{"reference_date":"2026-05-01"}]},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Estado do serviço.","examples":["ok"]},"service":{"type":"string","title":"Service","description":"Nome da aplicação.","examples":["My Finance API"]},"environment":{"type":"string","title":"Environment","description":"Ambiente de execução (`development`, `staging`, `production`).","examples":["development"]}},"type":"object","required":["status","service","environment"],"title":"HealthResponse","description":"Resposta do health check.","examples":[{"environment":"development","service":"My Finance API","status":"ok"}]},"LoanOrInvestmentCreateRequest":{"properties":{"type":{"type":"string","enum":["loan","investment"],"title":"Type"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"balance":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Balance","default":"0"},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Details"}},"type":"object","required":["type","name"],"title":"LoanOrInvestmentCreateRequest","description":"Cria um registro de empréstimo (`loan`) ou investimento (`investment`).","examples":[{"balance":"12500.00","details":"Compra em 2026-04-01","name":"Tesouro Selic 2030","type":"investment"}]},"LoanOrInvestmentResponse":{"properties":{"id":{"type":"integer","title":"Id"},"type":{"type":"string","title":"Type"},"name":{"type":"string","title":"Name"},"balance":{"type":"number","title":"Balance"},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Details"}},"type":"object","required":["id","type","name","balance","details"],"title":"LoanOrInvestmentResponse"},"LoanOrInvestmentUpdateRequest":{"properties":{"type":{"anyOf":[{"type":"string","enum":["loan","investment"]},{"type":"null"}],"title":"Type"},"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"balance":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Balance"},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Details"}},"type":"object","title":"LoanOrInvestmentUpdateRequest"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest","description":"Credenciais para obter par de tokens (`access` + `refresh`).","examples":[{"email":"joao.silva@example.com","password":"SenhaForte123!"}]},"MatchCreateRequest":{"properties":{"manual_transaction_id":{"type":"integer","title":"Manual Transaction Id"},"imported_transaction_id":{"type":"integer","title":"Imported Transaction Id"}},"type":"object","required":["manual_transaction_id","imported_transaction_id"],"title":"MatchCreateRequest","description":"Confirma um match (usuário aceita a sugestão ou cria manualmente).","examples":[{"imported_transaction_id":250,"manual_transaction_id":100}]},"MatchResponse":{"properties":{"id":{"type":"integer","title":"Id"},"manual_transaction_id":{"type":"integer","title":"Manual Transaction Id"},"imported_transaction_id":{"type":"integer","title":"Imported Transaction Id"},"status":{"type":"string","enum":["confirmed","suggested","rejected"],"title":"Status"},"confidence_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence Score"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","manual_transaction_id","imported_transaction_id","status","confidence_score","created_at"],"title":"MatchResponse"},"MessageResponse":{"properties":{"message":{"type":"string","title":"Message"}},"type":"object","required":["message"],"title":"MessageResponse","description":"Resposta genérica com mensagem em pt-BR.","examples":[{"message":"E-mail verificado com sucesso."}]},"PeriodSummary":{"properties":{"period":{"type":"string","title":"Period","description":"`YYYY-MM` (mensal) ou `YYYY-MM-DD` (diário)."},"expenses":{"type":"number","title":"Expenses"},"income":{"type":"number","title":"Income"},"net":{"type":"number","title":"Net","description":"`income - expenses`."}},"type":"object","required":["period","expenses","income","net"],"title":"PeriodSummary","description":"Linha de série temporal (uma por dia ou por mês, conforme `period`).","examples":[{"expenses":2596.4,"income":4500.0,"net":1903.6,"period":"2026-05"}]},"PluggyCredentialsCreateRequest":{"properties":{"client_id":{"type":"string","maxLength":255,"minLength":1,"title":"Client Id","description":"`clientId` da Pluggy (obtido no console Pluggy do cliente)."},"client_secret":{"type":"string","maxLength":255,"minLength":1,"title":"Client Secret","description":"`clientSecret` correspondente. Armazenado **criptografado** (Fernet)."}},"type":"object","required":["client_id","client_secret"],"title":"PluggyCredentialsCreateRequest","description":"Salva o par `clientId`/`clientSecret` da conta Pluggy do usuário (BYOK).","examples":[{"client_id":"abcdef12-3456-7890-abcd-ef1234567890","client_secret":"super-secret-value"}]},"PluggyCredentialsResponse":{"properties":{"client_id":{"type":"string","title":"Client Id"},"client_secret":{"type":"string","title":"Client Secret","description":"Retornado descriptografado para permitir que o app exiba ofuscado (apenas o usuário dono vê)."},"item_ids":{"items":{"type":"string"},"type":"array","title":"Item Ids","description":"UUIDs dos Items Pluggy descobertos a partir das contas/cartões do usuário (campo `pluggy_item_id`)."},"status":{"type":"string","enum":["active","sync_failed"],"title":"Status"},"last_sync_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Sync At"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["client_id","client_secret","item_ids","status","last_sync_at","last_error","created_at"],"title":"PluggyCredentialsResponse","description":"Credenciais Pluggy salvas + estado da última sincronização.","examples":[{"client_id":"abcdef12-3456-7890-abcd-ef1234567890","client_secret":"super-secret-value","created_at":"2026-05-01T10:00:00","item_ids":["11111111-2222-3333-4444-555555555555"],"last_sync_at":"2026-05-08T20:00:00","status":"active"}]},"PluggySyncRequest":{"properties":{"item_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Item Ids","description":"UUIDs dos Items Pluggy (conexões) a sincronizar. Unidos aos `pluggy_item_id` já vinculados em contas/cartões do usuário. Se vazio, sincroniza apenas os já vinculados."}},"type":"object","title":"PluggySyncRequest","description":"Body opcional para `POST /v1/pluggy/sync`.","examples":[{"item_ids":["11111111-2222-3333-4444-555555555555"]}]},"PluggySyncResponse":{"properties":{"status":{"type":"string","enum":["success","failed","partial"],"title":"Status","description":"`success` se importou tudo sem erros, `partial` se importou algo mas houve falhas em ao menos um item, `failed` se nada foi importado."},"accounts_imported":{"type":"integer","title":"Accounts Imported","description":"Contas + cartões criados.","default":0},"transactions_imported":{"type":"integer","title":"Transactions Imported","description":"Transações criadas (não duplicadas).","default":0},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Mensagem agregando erros (até 2000 chars)."}},"type":"object","required":["status"],"title":"PluggySyncResponse","description":"Resultado da sincronização.","examples":[{"accounts_imported":2,"status":"success","transactions_imported":137}]},"ProjectionPoint":{"properties":{"date":{"type":"string","format":"date","title":"Date"},"balance":{"type":"number","title":"Balance"},"events":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Events","description":"Eventos que afetam o saldo neste dia. Cada item:\n- `type`: `commitment` (compromisso a pagar/receber).\n- `description`: texto do compromisso.\n- `amount`: valor (negativo se sai, positivo se entra)."}},"type":"object","required":["date","balance","events"],"title":"ProjectionPoint","description":"Ponto da projeção de saldo: data + saldo previsto + eventos no dia.","examples":[{"balance":4250.0,"date":"2026-05-15","events":[{"amount":-1500.0,"description":"Aluguel","type":"commitment"}]}]},"ProjectionResponse":{"properties":{"account_id":{"type":"integer","title":"Account Id"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"series":{"items":{"$ref":"#/components/schemas/ProjectionPoint"},"type":"array","title":"Series","description":"Pontos da projeção. Sempre inclui o dia atual e o último do horizonte. Pontos intermediários só aparecem em datas com eventos ou no dia 1 do mês."}},"type":"object","required":["account_id","start_date","end_date","series"],"title":"ProjectionResponse","description":"Projeção de saldo da conta no horizonte solicitado."},"ReconciliationSuggestion":{"properties":{"manual_transaction_id":{"type":"integer","title":"Manual Transaction Id","description":"Transação manual (sem `source=pluggy`)."},"candidate_transaction_id":{"type":"integer","title":"Candidate Transaction Id","description":"Transação importada da Pluggy."},"confidence_score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence Score","description":"Score 0..1 calculado por heurística: mesmo valor (0.4), mesma data (0.3), descrição similar (0.3), overlap de palavras (0.1). Apenas pares com score ≥ 0.5 são retornados."},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons","description":"Lista das heurísticas que bateram: `same_amount`, `similar_amount`, `same_date`, `close_date`, `similar_description`, `some_words_match`."}},"type":"object","required":["manual_transaction_id","candidate_transaction_id","confidence_score","reasons"],"title":"ReconciliationSuggestion","description":"Sugestão automática de match entre lançamento manual e importado.","examples":[{"candidate_transaction_id":250,"confidence_score":0.85,"manual_transaction_id":100,"reasons":["same_amount","same_date","similar_description"]}]},"RecurringTemplateCreateRequest":{"properties":{"kind":{"type":"string","const":"commitment","title":"Kind"},"frequency":{"type":"string","const":"monthly","title":"Frequency"},"interval":{"type":"integer","maximum":12.0,"minimum":1.0,"title":"Interval","description":"A cada N meses.","default":1},"commitment_type":{"anyOf":[{"type":"string","enum":["payable","receivable"]},{"type":"null"}],"title":"Commitment Type"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Amount"},"description":{"type":"string","maxLength":255,"minLength":1,"title":"Description"},"anchor_date":{"type":"string","format":"date","title":"Anchor Date","description":"Primeira data — todas as ocorrências derivam dela."},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id","description":"Conta padrão para compromissos gerados pelo template."},"reminder_days_before":{"anyOf":[{"type":"integer","maximum":60.0,"minimum":0.0},{"type":"null"}],"title":"Reminder Days Before"}},"type":"object","required":["kind","frequency","amount","description","anchor_date"],"title":"RecurringTemplateCreateRequest","description":"Template para gerar compromissos recorrentes (apenas mensal nesta fase).","examples":[{"amount":"1500.00","anchor_date":"2026-05-15","commitment_type":"payable","description":"Aluguel","frequency":"monthly","interval":1,"kind":"commitment","reminder_days_before":3}]},"RecurringTemplateResponse":{"properties":{"id":{"type":"integer","title":"Id"},"kind":{"type":"string","title":"Kind"},"frequency":{"type":"string","title":"Frequency"},"interval":{"type":"integer","title":"Interval"},"commitment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commitment Type"},"amount":{"type":"number","title":"Amount"},"description":{"type":"string","title":"Description"},"anchor_date":{"type":"string","format":"date","title":"Anchor Date"},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"},"reminder_days_before":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reminder Days Before"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","kind","frequency","interval","commitment_type","amount","description","anchor_date","account_id","reminder_days_before","is_active"],"title":"RecurringTemplateResponse"},"RecurringTemplateUpdateRequest":{"properties":{"kind":{"anyOf":[{"type":"string","const":"commitment"},{"type":"null"}],"title":"Kind"},"frequency":{"anyOf":[{"type":"string","const":"monthly"},{"type":"null"}],"title":"Frequency"},"interval":{"anyOf":[{"type":"integer","maximum":12.0,"minimum":1.0},{"type":"null"}],"title":"Interval"},"commitment_type":{"anyOf":[{"type":"string","enum":["payable","receivable"]},{"type":"null"}],"title":"Commitment Type"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount"},"description":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Description"},"anchor_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Anchor Date"},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"},"reminder_days_before":{"anyOf":[{"type":"integer","maximum":60.0,"minimum":0.0},{"type":"null"}],"title":"Reminder Days Before"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"RecurringTemplateUpdateRequest"},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","minLength":1,"title":"Refresh Token","description":"JWT de renovação ainda válido."}},"type":"object","required":["refresh_token"],"title":"RefreshRequest","description":"Body para `POST /v1/auth/refresh`.","examples":[{"refresh_token":"eyJhbGciOiJIUzI1NiIs..."}]},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"E-mail válido. Será o login."},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password","description":"Senha entre 8 e 128 caracteres."}},"type":"object","required":["email","password"],"title":"RegisterRequest","description":"Cadastro inicial — gera o usuário e dispara e-mail de verificação.","examples":[{"email":"joao.silva@example.com","password":"SenhaForte123!"}]},"RegisterResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"ID interno do usuário criado."},"email":{"type":"string","format":"email","title":"Email"},"email_verified":{"type":"boolean","title":"Email Verified","description":"Sempre `false` logo após o cadastro até a verificação."},"verification_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Token","description":"**Apenas em ambientes não-produção.** Token de verificação devolvido no corpo da resposta para facilitar testes — em produção é enviado exclusivamente por e-mail."}},"type":"object","required":["id","email","email_verified"],"title":"RegisterResponse","examples":[{"email":"joao.silva@example.com","email_verified":false,"id":42,"verification_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}]},"RejectionCreateRequest":{"properties":{"manual_transaction_id":{"type":"integer","title":"Manual Transaction Id"},"imported_transaction_id":{"type":"integer","title":"Imported Transaction Id"}},"type":"object","required":["manual_transaction_id","imported_transaction_id"],"title":"RejectionCreateRequest","description":"Rejeita uma sugestão para que ela não seja sugerida novamente."},"RejectionResponse":{"properties":{"id":{"type":"integer","title":"Id"},"manual_transaction_id":{"type":"integer","title":"Manual Transaction Id"},"imported_transaction_id":{"type":"integer","title":"Imported Transaction Id"},"rejected_at":{"type":"string","format":"date-time","title":"Rejected At"}},"type":"object","required":["id","manual_transaction_id","imported_transaction_id","rejected_at"],"title":"RejectionResponse"},"ReportSummaryResponse":{"properties":{"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"total_expenses":{"type":"number","title":"Total Expenses"},"total_income":{"type":"number","title":"Total Income"},"net_balance":{"type":"number","title":"Net Balance","description":"`total_income - total_expenses`."},"by_category":{"items":{"$ref":"#/components/schemas/CategorySummary"},"type":"array","title":"By Category"}},"type":"object","required":["start_date","end_date","total_expenses","total_income","net_balance","by_category"],"title":"ReportSummaryResponse","description":"Resumo agregado do período (entradas, saídas, saldo, por categoria).","examples":[{"by_category":[{"amount":845.3,"category_id":7,"category_name":"Alimentação","percentage":32.55}],"end_date":"2026-05-31","net_balance":1903.6,"start_date":"2026-05-01","total_expenses":2596.4,"total_income":4500.0}]},"ResetPasswordRequest":{"properties":{"token":{"type":"string","maxLength":512,"minLength":1,"title":"Token","description":"Token recebido por e-mail."},"new_password":{"type":"string","maxLength":128,"minLength":8,"title":"New Password","description":"Nova senha (8 a 128 caracteres)."}},"type":"object","required":["token","new_password"],"title":"ResetPasswordRequest","description":"Aplica nova senha usando o token enviado em `forgot-password`.","examples":[{"new_password":"NovaSenhaForte456!","token":"abc123_def456_..."}]},"TokenPairResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT de acesso curto (15 min por padrão)."},"refresh_token":{"type":"string","title":"Refresh Token","description":"JWT de renovação longo (30 dias por padrão)."},"token_type":{"type":"string","title":"Token Type","description":"Sempre `bearer`.","default":"bearer"}},"type":"object","required":["access_token","refresh_token"],"title":"TokenPairResponse","description":"Par JWT retornado pelos endpoints de login e refresh.","examples":[{"access_token":"eyJhbGciOiJIUzI1NiIs...","refresh_token":"eyJhbGciOiJIUzI1NiIs...","token_type":"bearer"}]},"TransactionCreateRequest":{"properties":{"type":{"type":"string","enum":["expense","income","transfer","card_expense","card_payment"],"title":"Type","description":"Tipo do lançamento: `expense` (despesa em conta), `income` (receita), `transfer` (transferência entre contas — exige `transfer_account_id`), `card_expense` (compra no cartão — exige `credit_card_id`), `card_payment` (pagamento de fatura — exige `credit_card_id`)."},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Amount","description":"Valor positivo (Decimal)."},"transaction_date":{"type":"string","format":"date","title":"Transaction Date","description":"Data do lançamento (ISO `YYYY-MM-DD`)."},"description":{"type":"string","maxLength":255,"minLength":1,"title":"Description"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Observação livre."},"account_id":{"type":"integer","title":"Account Id","description":"Conta de origem (sempre obrigatória)."},"credit_card_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Card Id","description":"Obrigatório quando `type` for `card_expense` ou `card_payment`."},"transfer_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Transfer Account Id","description":"Obrigatório quando `type` for `transfer`."},"category_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Category Id","description":"Categoria do usuário."},"installment_count":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Installment Count","description":"Total de parcelas (se for compra parcelada)."},"installment_index":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Installment Index","description":"Número desta parcela (1..installment_count)."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Origem (`pluggy` quando vier do Open Finance, vazio para manual)."},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"ID externo (idempotência)."}},"type":"object","required":["type","amount","transaction_date","description","account_id"],"title":"TransactionCreateRequest","description":"Cria um lançamento financeiro (expense / income / transfer / card_*).","examples":[{"account_id":1,"amount":"45.90","category_id":7,"description":"Almoço executivo","transaction_date":"2026-05-08","type":"expense"}]},"TransactionResponse":{"properties":{"id":{"type":"integer","title":"Id"},"type":{"type":"string","title":"Type"},"amount":{"type":"number","title":"Amount"},"transaction_date":{"type":"string","format":"date","title":"Transaction Date"},"description":{"type":"string","title":"Description"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"account_id":{"type":"integer","title":"Account Id"},"credit_card_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Card Id"},"transfer_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Transfer Account Id"},"category_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Category Id"},"category_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Name"},"installment_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Installment Count"},"installment_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Installment Index"},"installment_group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Installment Group"},"recurring_template_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recurring Template Id"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","type","amount","transaction_date","description","note","account_id","credit_card_id","transfer_account_id","category_id","category_name","installment_count","installment_index","installment_group","recurring_template_id","source","external_id","created_at"],"title":"TransactionResponse","examples":[{"account_id":1,"amount":45.9,"category_id":7,"category_name":"Alimentação","created_at":"2026-05-08T20:15:00Z","description":"Almoço executivo","id":1024,"transaction_date":"2026-05-08","type":"expense"}]},"TransactionUpdateRequest":{"properties":{"type":{"anyOf":[{"type":"string","enum":["expense","income","transfer","card_expense","card_payment"]},{"type":"null"}],"title":"Type"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount"},"transaction_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Transaction Date"},"description":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Description"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Account Id"},"credit_card_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Card Id"},"transfer_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Transfer Account Id"},"category_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Category Id"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"}},"type":"object","title":"TransactionUpdateRequest"},"UserProfileResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"ID interno do usuário."},"email":{"type":"string","format":"email","title":"Email","description":"E-mail de login."},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Nome completo informado pelo usuário."},"tax_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tax Id","description":"CPF (11 dígitos) ou CNPJ (14 dígitos), só números."},"birth_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Birth Date","description":"Data de nascimento (ISO `YYYY-MM-DD`)."}},"type":"object","required":["id","email","full_name","tax_id","birth_date"],"title":"UserProfileResponse","description":"Perfil completo do usuário autenticado.","examples":[{"birth_date":"1990-05-20","email":"joao.silva@example.com","full_name":"João da Silva","id":42,"tax_id":"12345678909"}]},"UserProfileUpdate":{"properties":{"full_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Full Name","description":"Nome completo. String vazia limpa o campo."},"tax_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tax Id","description":"CPF ou CNPJ (com ou sem máscara — é normalizado). Validação completa de dígitos. `null` ou vazio limpa o campo."},"birth_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Birth Date","description":"Data ISO `YYYY-MM-DD`."}},"type":"object","title":"UserProfileUpdate","description":"Patch parcial do perfil — apenas os campos enviados são aplicados.","examples":[{"birth_date":"1990-05-20","full_name":"João da Silva","tax_id":"123.456.789-09"}]},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValidationErrorItem":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Loc","description":"Caminho até o campo inválido.","examples":[["body","email"]]},"msg":{"type":"string","title":"Msg","description":"Mensagem de validação.","examples":["value is not a valid email address"]},"type":{"type":"string","title":"Type","description":"Tipo do erro.","examples":["value_error.email"]}},"type":"object","required":["loc","msg","type"],"title":"ValidationErrorItem"},"ValidationErrorResponse":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationErrorItem"},"type":"array","title":"Detail"}},"type":"object","required":["detail"],"title":"ValidationErrorResponse","description":"Erro 422 — falha de validação Pydantic."},"VerifyEmailRequest":{"properties":{"token":{"type":"string","title":"Token","description":"JWT recebido no e-mail de verificação (ou em `verification_token`)."}},"type":"object","required":["token"],"title":"VerifyEmailRequest","description":"Confirma o e-mail do usuário usando o token enviado.","examples":[{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}]}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT obtido em `POST /v1/auth/login`. Cole apenas o token (sem o prefixo `Bearer`)."}}},"tags":[{"name":"auth","description":"**Autenticação e gestão de credenciais.** Cadastro, verificação de e-mail, login (Bearer JWT), renovação de token, esqueci/redefinir senha. É o ponto de entrada para qualquer outro endpoint de negócio."},{"name":"users","description":"**Perfil do usuário autenticado.** Leitura e atualização de dados pessoais (nome, CPF/CNPJ, data de nascimento) e troca de senha. Todos os endpoints exigem o Bearer Token."},{"name":"financial","description":"**Núcleo financeiro.** Contas (`accounts`), categorias com hierarquia (`categories`), lançamentos (`transactions`) com suporte a parcelas e transferências, e regras de classificação automática (`classification-rules`)."},{"name":"support","description":"**Entidades de apoio ao financeiro.** Cartões de crédito com fechamento/vencimento, ciclos de fatura, compromissos (`commitments`) a pagar/receber, templates de recorrência mensal e empréstimos/investimentos."},{"name":"reports","description":"**Relatórios e projeções.** Resumo agregado por período, séries temporais diárias/mensais e projeção de saldo futuro com base em compromissos."},{"name":"pluggy","description":"**Integração Pluggy (Open Finance).** Credenciais BYOK criptografadas, sincronização de contas/transações por `itemId`, webhook idempotente com processamento em background, e endpoints de conciliação manual × importado."},{"name":"health","description":"**Saúde do serviço.** Endpoint público para health checks de infraestrutura (load balancers, monitoramento, smoke tests)."}]}