# API — contratos > Historical prototype contracts only. The active local API is documented at > [localhost:8000/docs](http://localhost:8000/docs) and in [LOCAL_SETUP.md](LOCAL_SETUP.md). > `CONTEXT.md` overrides the legacy endpoints and workflows below. > Todos os endpoints do módulo DTF, com requisição e resposta. > Base: `portal/main.py` e `kanban/main.py`. --- ## Autenticação | Quem chama | Como se autentica | |---|---| | Tiny (webhook) | header `x-token` com `WEBHOOK_TOKEN` | | Cliente | token no path da URL — sem login | | Agente | header `x-token` com `AGENTE_TOKEN` | | Kanban (aba do PCP) | sessão do PCP — **rede interna, sem porta para fora** | --- # PORTAL · na nuvem ## `POST /webhook/tiny` Chamado quando nasce um pedido de DTF. **É o gatilho de tudo.** ```json { "numero": "48213", "cliente": "Estamparia Vitória", "telefone": "5516999998888", "metros": 2.4, "cliente_novo": false } ``` **200** ```json { "ok": true, "link": "https://arte.dropstaratacado.com.br/arte/a7f3k9..." } ``` Cria o pedido, gera token de 7 dias e enfileira o WhatsApp com o link. Se o número já existir, devolve `{"ok":true,"ja_existia":true}` sem duplicar. **Latência importa.** O link só sai depois que o pedido existe no Tiny — se a integração VNDA → Tiny demorar, o cliente espera. Medir antes de subir. --- ## `GET /arte/{token}` Página de upload. HTML. O token já sabe o pedido — **o cliente não digita nada.** ## `GET /api/arte/{token}` ```json { "pedido": "48213", "cliente": "Estamparia Vitória", "metros": 2.4, "area_util_cm": 97, "largura_max_cm": 57, "limite_arquivo_m": 20, "max_gb": 5, "max_arquivos": 10 } ``` **410** se o token expirou. ## `POST /api/arte/{token}/url` ```json { "nome": "estampa.png", "bytes": 84000000 } ``` ```json { "url": "https://r2.../48213/ab12_estampa.png?X-Amz-...", "chave": "48213/ab12_estampa.png", "expira_em": 3600 } ``` **Upload direto para o storage.** Arquivo de 5 GB passando pelo VPS derrubaria o processo. Em partes, para arquivos grandes. ## `POST /api/arte/{token}/pronto` ```json { "chave": "48213/ab12_estampa.png", "repeticoes": 20 } ``` ```json { "arte_id": 991, "status": "processando" } ``` Dispara o pré-flight em background. O cliente acompanha por polling em `GET /api/arte/{token}/status`. ## `GET /api/arte/{token}/status` **Aprovada:** ```json { "status": "aprovada", "dpi_efetivo": 300, "qualidade_pct": 100, "metros_totais": 48.0, "minutos_maquina": 144, "partes": [{"ordem":1,"metros":20,"nome":"48213_p1.png"}, {"ordem":2,"metros":20,"nome":"48213_p2.png"}, {"ordem":3,"metros":8,"nome":"48213_p3_carimbado.png"}], "avisos": [], "previsao_saida": "2026-09-02T18:00:00-03:00" } ``` **Recusada:** ```json { "status": "recusada", "motivo": "A resolução real da arte é de 72 DPI no tamanho que você comprou. Precisamos de pelo menos 150 DPI — o ideal é 300. Reenvie em maior resolução." } ``` **O relógio do prazo só começa quando `status = aprovada`.** Arquivo ruim enviado às 17h não faz as horas correrem contra a casa. --- ## `GET /api/artes` · o agente busca Header `x-token`. Devolve o que está aprovado, com vírus liberado e ainda não baixado. ```json [{ "arte_id": 991, "pedido": "48213", "cliente": "Estamparia Vitória", "metros": 48.0, "minutos_maquina": 144, "conferir_manual": false, "partes": [{"ordem":1,"metros":20,"nome":"48213_p1.png", "url":"https://r2.../...","sha256":"a3f9..."}] }] ``` ## `POST /api/artes/{id}/baixada` · confirma o download ## `POST /api/agente/heartbeat` · a cada 5 min **Sem sinal por 15 minutos, alertar o TI.** Serviço silencioso parado é pior que erro barulhento: ninguém percebe até o cliente cobrar. --- ## `GET /cliente/{token_cliente}` · minhas artes Link permanente do cliente, não do pedido. Lista as artes tratadas dos últimos 12 meses. ## `POST /cliente/{token_cliente}/reimprimir` ```json { "arte_id": 812, "metros": 20 } ``` Abre pedido novo no Tiny com a arte já pronta. **Recompra sem atrito.** --- # KANBAN · rede interna, aba do PCP ## `GET /api/quadro` ```json { "colunas": [{"id":"rec","nome":"Arte recebida"}, ...], "cards": { "fil": [{ "arte_id": 991, "pedido": "48213", "cliente": "Estamparia Vitória", "metros": 48.0, "minutos_maquina": 144, "partes": 3, "maquina": null, "desde": "2026-09-02T14:02:00", "parado_seg": 4320, "conferir_manual": false }] }, "maquinas": [{ "n": 1, "ocupada": true, "pedido": "48190", "metros": 9.0, "rodando_seg": 720 }, { "n": 3, "ocupada": false }] } ``` `maquinas` é o que pinta o **círculo vermelho**. Com seis máquinas vendo o mesmo quadro, é o que evita dois operadores no mesmo arquivo. --- ## `POST /api/puxar` ```json { "maquina": 4, "usuario": "alexandre" } ``` ```json { "maquina": "Maq 4", "pedido": "48197", "metros": 18.0, "minutos": 60, "reserva_expira_em": 3 } ``` **Um pedido por vez** e **reserva de 3 minutos** — ajustes pedidos pela sala. Pega sempre o mais antigo da fila. Se não entrar em `imp` em 3 min, volta. **409** se a máquina já estiver ocupada. **404** se a fila estiver vazia. ## `POST /api/devolver` ```json { "arte_id": 991, "usuario": "thales", "motivo": "travou no meio" } ``` Volta ao **topo** da fila, não ao fim — o pedido já esperou uma vez. ## `POST /api/mover` ```json { "arte_id": 991, "para": "fin", "usuario": "poliana", "maquina": 4 } ``` Grava o movimento **antes** de qualquer integração externa. Se o Tiny estiver fora, o job fica na fila e tenta de novo em 1, 5 e 30 min — **mas a medição de tempo já está salva.** Move o arquivo entre as pastas e enfileira marcador e WhatsApp quando a coluna pedir. Ao ir para `apc`, pergunta o motivo da prova de cor. ## `PATCH /api/card/{arte_id}` ```json { "minutos_maquina": 90 } ``` Ajuste da estimativa. **O sistema sugere por `metros/20*60`; quem trata a arte corrige só quando foge do normal.** É esse número que soma a fila contra a capacidade do dia. ## `GET /p/{pedido}` O QR do carimbo aponta para cá. Redireciona para o card. **A revisão bipa e cai na tela do pedido** em vez de procurar na lista. --- ## Retrabalho ### `POST /api/retrabalho` ```json { "pedido_origem": "48184", "metros": 2.0, "causa": "impressao", "aberto_por": "mayana", "evidencia": "print-whatsapp.jpg" } ``` ```json { "id": 44, "alcada": "sala", "vez": 1, "conta_na_meta": true } ``` **A Mayana abre e classifica** — conhece o cliente e a reclamação. **A causa fica travada.** Quem discorda contesta, não altera. ### `POST /api/retrabalho/{id}/autorizar` ```json { "usuario": "thales" } ``` **Thales ou Alexandre autorizam**, porque o retrabalho da casa entra na meta deles. Eles confirmaram achar justo. **403** se a alçada exigir financeiro ou diretoria. ### `POST /api/retrabalho/{id}/contestar` ```json { "usuario": "alexandre", "texto": "não foi impressão, a arte veio em CMYK" } ``` Registra a discordância sem mudar a causa. Fica com quem pediu e quem decidiu. --- ## Relatórios ### `GET /api/relatorio/etapas?dias=7` ```json [{ "coluna": "tra", "nome": "Arte tratada", "movimentos": 84, "media_min": 94, "pior_min": 380 }] ``` **Responde a pergunta que decide o terceiro turno:** quanto do tempo é fila e quanto é trabalho. Se a arte demora 6h e a máquina imprime em 40 min, rodar 24h só faz a fila esperar de madrugada. ### `GET /api/relatorio/impressao?dias=7` ```json [{ "maquina": "Maq 1", "pedidos": 42, "media_min": 34, "metros": 310.5, "m_por_hora": 20.4 }] ``` **`m_por_hora` valida ou derruba os 20 m/h.** Todo o cálculo de capacidade — 60.480 metros/mês, R$ 148 mil de ganho — depende desse número. Se for 14, a conta muda inteira. ### `GET /api/relatorio/aproveitamento?dias=30` ```json { "metros_faturados": 11605, "metros_de_filme": 12693, "aproveitamento_pct": 91.4, "valor_do_ponto_mes": 980, "fonte": "transferências para o depósito Sala DTF no Tiny" } ``` **Não exige apontamento novo.** O consumo já é registrado quando o insumo é transferido para o depósito de impressão — a Altus faz isso porque também revende insumo. ### `GET /api/relatorio/retrabalho?dias=30` ```json { "por_causa": [{ "causa": "impressao", "descricao": "Falha de impressão", "responsavel": "Thales e Alexandre", "pedidos": 9, "metros": 24.5, "pct": 1.1, "meta": 0.4, "bate": false }], "total_casa_pct": 2.9, "meta_casa_pct": 1.6 } ``` **⚠ A meta ainda não está decidida.** A proposta era 0,4% por causa; a sala respondeu que "meta geral seria melhor". `META_POR_CAUSA` está isolado no código para trocar sem mexer no resto. --- ## Códigos de erro | Código | Quando | |---|---| | 400 | payload inválido, coluna inexistente, mais de 10 arquivos | | 401 | token do webhook ou do agente errado | | 403 | alçada insuficiente para autorizar retrabalho | | 404 | token não existe, card não encontrado, fila vazia | | 409 | máquina já ocupada, pedido já reservado | | 410 | token do link expirou | | 413 | arquivo acima de 5 GB | | 429 | rate limit — 20 req/min por token | --- ## O que testar antes de dar por pronto - [ ] Upload de arquivo de 5 GB sem derrubar o VPS - [ ] Dois operadores clicando `puxar` ao mesmo tempo — só um pega - [ ] Reserva expirando: puxar e não mover em 3 min devolve à fila - [ ] Tiny fora do ar: o movimento grava e o job fica na fila - [ ] Agente sem internet: para, e ao voltar baixa o acumulado - [ ] Arquivo baixado pela metade não aparece no kanban - [ ] Token expirado devolve 410 com mensagem clara ao cliente