Backup do banco
Backup/exportação do banco MySQL pelo Hub. Um administrador pede um backup em JSON ou SQL, com escopo todo, tabelas ou parcial (por período e/ou lote de linhas). A rota grava um job pendente e enfileira uma task Celery; o worker gera um .zip no servidor, que fica disponível para download por BACKUP_RETENCAO_DIAS dias. O download é feito por token de uso único.
Dois routers no mesmo prefixo
O prefixo /backups monta dois routers com proteções diferentes. O router de operação usa _SCOPE_DEP e, rota a rota, o guard somente_admin_humano (administrador com JWT de pessoa; API Key recebe 403). O router de download é público (sem Bearer): o Hub responde 302 para a URL de download e o navegador não envia header no redirect, então a única credencial é o token de uso único (TTL de 60 s, consumido no primeiro uso).
Base path
Todos os endpoints abaixo são montados sob o prefixo:
/api/v1/backupsA URL completa em produção é https://api.contabilidadealcance.com.br/api/v1/backups.
Escopos necessários
| Escopo | Operações |
|---|---|
backups:read | GET /, GET /tabelas, GET /{job_id} |
backups:write | POST /, POST /{job_id}/download-token, DELETE /{job_id} |
| (nenhum) | GET /{job_id}/download - sem Bearer, autenticado só pelo token na query |
O escopo é derivado da rota (prefixo /backups → recurso backups; GET = read, POST/DELETE = write). Além do escopo, todas as rotas do router de operação exigem perfil = administrador e token_type == "user".
Endpoints
GET /backups/tabelas
Catálogo de tabelas exportáveis, lido do information_schema do MySQL (inclui tabelas sem model mapeado). É a lista usada pelo multiselect do Hub e pela validação do POST.
Parâmetros
Este endpoint não recebe parâmetros.
Request
Sem corpo de requisição.
Response 200 OK
{
"status": "success",
"message": "Tabelas recuperadas com sucesso",
"data": [
{
"nome": "lembrete",
"linhas_estimadas": 18420,
"colunas_data": ["data_hora_envio", "data_hora_criacao"]
}
]
}linhas_estimadas é a estatística TABLE_ROWS do InnoDB (aproximada). colunas_data lista as colunas DATE/DATETIME/TIMESTAMP - as únicas aceitas em periodo.coluna. Escopo: backups:read + administrador com JWT de pessoa
GET /backups/
Lista os backups, mais recentes primeiro.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
limit | int | query | não | Quantidade máxima (padrão 50, entre 1 e 200). |
Request
Sem corpo de requisição.
Response 200 OK
{
"status": "success",
"message": "Backups recuperados com sucesso",
"data": [
{
"id": 31,
"formato": "sql",
"escopo": "parcial",
"tabelas": ["lembrete"],
"periodo": { "coluna": "data_hora_criacao", "de": "2026-09-01", "ate": "2026-09-30" },
"lote_linhas": null,
"compactar": true,
"status": "concluido",
"progresso_pct": 100,
"etapa": null,
"tamanho_bytes": 482133,
"sha256": "9f2c4e7a1b0d3c5e6f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e",
"nome_arquivo": "backup_31.zip",
"erro": null,
"solicitante_id": 7,
"solicitante_login": "admin.hub",
"ip": "10.0.0.15",
"criado_em": "2026-10-14T10:00:00",
"iniciado_em": "2026-10-14T10:00:03",
"concluido_em": "2026-10-14T10:00:41",
"expira_em": "2026-10-21T10:00:41"
}
]
}Escopo: backups:read + administrador com JWT de pessoa
POST /backups/
Solicita um backup. A geração é assíncrona: a resposta volta com o job em pendente e o progresso é acompanhado por GET /backups/{job_id}. Rate limit de 10 requisições por 60 s (bucket backups-write).
Parâmetros
Sem parâmetros de rota ou query. Corpo BackupJobCreate:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
formato | str | sim | json ou sql. |
escopo | str | sim | todo, tabelas ou parcial. |
tabelas | list[str] | condicional | Obrigatória (ao menos 1) em tabelas/parcial; proibida em todo. Máx. 500, sem repetição. |
periodo | PeriodoBackup | condicional | Só em parcial: coluna (DATE/DATETIME), de, ate (ate não pode ser anterior a de). |
lote_linhas | int | condicional | Só em parcial, entre 1000 e 1000000. |
compactar | bool | não | Padrão true. Nesta versão o backup é sempre .zip; false retorna 422. |
O escopo parcial exige periodo e/ou lote_linhas. As tabelas precisam existir no catálogo e, com periodo, ao menos uma delas precisa ter a coluna informada.
Request
{
"formato": "sql",
"escopo": "parcial",
"tabelas": ["lembrete"],
"periodo": { "coluna": "data_hora_criacao", "de": "2026-09-01", "ate": "2026-09-30" }
}Response 202 Accepted
Envelope com o BackupJobOut recém-criado (status: "pendente", progresso_pct: 0) e a mensagem "Backup enfileirado".
Erros: 422 (validação estrutural ou tabela/coluna inexistente), 503 (fila indisponível - o job fica gravado com status: "falha"). Escopo: backups:write + administrador com JWT de pessoa
GET /backups/{job_id}
Detalhe de um backup.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
job_id | int | path | sim | Id do job. |
Request
Sem corpo de requisição.
Response 200 OK
Envelope com um BackupJobOut e a mensagem "Backup recuperado". Job inexistente → 404.
Escopo: backups:read + administrador com JWT de pessoa
POST /backups/{job_id}/download-token
Gera um token de download de uso único, válido por 60 s, e devolve a URL pronta. O token fica no Redis e é consumido no primeiro download. A base da URL vem da configuração API_PUBLIC_URL; vazia, usa a base da própria requisição. A emissão gera um evento na trilha de auditoria (o token nunca é registrado).
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
job_id | int | path | sim | Id do job. |
Request
Sem corpo de requisição.
Response 200 OK
{
"status": "success",
"message": "Token gerado",
"data": {
"token": "q8V3m1lQe0J9x2R7tYk4Hc6Pn5Wb1Za8Ds0Fg3Lu2Io",
"expira_em": "2026-10-14T10:05:12",
"url": "https://api.contabilidadealcance.com.br/api/v1/backups/31/download?token=q8V3m1lQe0J9x2R7tYk4Hc6Pn5Wb1Za8Ds0Fg3Lu2Io"
}
}Erros: 404 (job inexistente), 409 (job ainda não concluido), 410 (expirado ou removido), 503 (Redis indisponível - não há fallback em memória). Escopo: backups:write + administrador com JWT de pessoa
GET /backups/{job_id}/download
Baixa o .zip do backup. Rota pública (sem Bearer, sem _SCOPE_DEP): a credencial é o token gerado no passo anterior, que só vale uma vez e precisa pertencer ao mesmo job_id. Rate limit de 30 requisições por 60 s por IP (bucket backups-download). Só o download bem-sucedido é gravado na trilha de auditoria (ator anônimo, com IP).
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
job_id | int | path | sim | Id do job. |
token | str | query | sim | Token de uso único (1 a 128 caracteres). |
Request
Sem corpo de requisição.
Response 200 OK
Arquivo binário (Content-Type: application/zip, Cache-Control: no-store), com o nome do arquivo no Content-Disposition.
Erros: 403 (token inválido, expirado, já utilizado ou de outro job), 410 (backup expirado, removido ou arquivo ausente no servidor), 503 (Redis indisponível). Escopo: nenhum - autenticação apenas pelo token
DELETE /backups/{job_id}
Remove o backup: apaga o arquivo do servidor e marca o job como removido (o registro permanece). Idempotente para job já removido.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
job_id | int | path | sim | Id do job. |
Request
Sem corpo de requisição.
Response 204 No Content
Sem corpo de resposta. Erros: 404 (job inexistente), 409 (job em executando). Escopo: backups:write + administrador com JWT de pessoa
Schemas
BackupJobOut
| Campo | Tipo | Descrição |
|---|---|---|
id | int | Id do job. |
formato | str | json ou sql. |
escopo | str | todo, tabelas ou parcial. |
tabelas | list[str] | Tabelas pedidas (vazia em todo). |
periodo | PeriodoBackup | null | coluna, de, ate. |
lote_linhas | int | null | Tamanho do lote, quando pedido. |
compactar | bool | Sempre true nesta versão. |
status | str | pendente, executando, concluido, falha, expirado ou removido. |
progresso_pct | int | Progresso de 0 a 100. |
etapa | str | null | Etapa corrente da geração. |
tamanho_bytes | int | null | Tamanho do .zip. |
sha256 | str | null | Hash do arquivo. |
nome_arquivo | str | null | Nome do arquivo gerado. |
erro | str | null | Mensagem curta quando status = falha. |
solicitante_id | int | Usuário que pediu. |
solicitante_login | str | null | Login do solicitante (snapshot). |
ip | str | null | IP da requisição. |
criado_em | datetime | Criação do job. |
iniciado_em | datetime | null | Início da execução. |
concluido_em | datetime | null | Conclusão. |
expira_em | datetime | null | Fim da disponibilidade para download. |
O caminho do arquivo no servidor nunca sai pela API.
DownloadTokenOut
| Campo | Tipo | Descrição |
|---|---|---|
token | str | Token de uso único. |
expira_em | datetime | Validade do token (emissão + 60 s). |
url | str | URL completa de GET /backups/{job_id}/download. |
Notas
- Ciclo de vida:
pendente→executando→concluido→expirado; ouexecutando→falha; e qualquer estado →removidoviaDELETE. - Colunas sensíveis mascaradas: o backup mantém as tabelas, mas anula colunas de segredo (
NULLno SQL,nullno JSON) - por exemplouser.senha,portal_user.senha_hash,api_key.key_hash,customer.senha_gov,integration_setting.api_key, os tokens da Cora/TecnoSpeed e a colunavalordeconfiguracao_sistema. A lista central fica emapp/domain/orquestracao/backup/sensiveis.py. - Datas: os campos de data do job são gravados em hora de São Paulo (naive).