Enviar arquivos por bucket S3
O canal por bucket é o envio automatizado de arquivos para a Zabit. O seu sistema grava o ZIP em uma pasta exclusiva sua, num bucket S3 da Zabit, usando uma identidade da sua conta AWS. A Zabit detecta o arquivo, verifica com antivírus, processa e grava um recibo em JSON dizendo o que aconteceu.
O canal é habilitado por cliente. Os valores entre <...> neste guia (bucket, identificador do
espaço de trabalho e chave de criptografia) são fornecidos pela Zabit na habilitação.
Como funciona a confiança
A confiança vai da Zabit para a sua identidade, e não o contrário:
- você cria uma role na sua conta AWS e nos informa o ARN dela;
- a Zabit autoriza essa role a gravar só na sua pasta do bucket, por política do bucket e da chave de criptografia;
- a Zabit não assume nenhuma role na sua conta, não usa
ExternalIde não entrega access key, usuário, senha, Client ID nem chave de API para o envio.
Quem pode assumir a sua role (o seu pipeline, um servidor, um agendador) é decisão sua, dentro da sua conta. As credenciais vêm do STS, são temporárias e a AWS as renova.
Um usuário IAM com access key fixa funciona, mas a chave não expira sozinha: se vazar, vale até alguém perceber e revogar. Com uma role, as credenciais são temporárias. Recomendamos a role.
1. O que você cria do seu lado
Crie uma role IAM na sua conta com a policy abaixo. Ela tem só o necessário para enviar:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnviarArquivosParaAZabit",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<BUCKET>/ws/<WSID>/entrada/*"
},
{
"Sid": "CifrarComAChaveDoCanal",
"Effect": "Allow",
"Action": ["kms:GenerateDataKey", "kms:Decrypt"],
"Resource": "<KMS_KEY_ARN>"
}
]
}
| Permissão | Para quê |
|---|---|
s3:PutObject em ws/<WSID>/entrada/* | Gravar o arquivo. É a única ação de S3 necessária para enviar. |
kms:GenerateDataKey na chave do canal | O envio é obrigatoriamente cifrado com a chave do canal (veja a seção 3). O S3 pede uma chave de dados ao KMS em nome da sua role. |
kms:Decrypt na chave do canal | Exigida pelo S3 nos envios em várias partes (multipart), que o AWS CLI e os SDKs usam automaticamente em arquivos grandes. Se você só envia com put-object de uma parte, pode omitir. |
Ler o retorno no bucket (opcional)
Se você quer ler o recibo e as pastas de retorno direto no bucket, acrescente os dois statements
abaixo e mantenha o kms:Decrypt. Se você acompanha o envio pelo
webhook ou pela API de status, não precisa deles: a role fica só com a
escrita.
{
"Sid": "LerORetornoDaZabit",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::<BUCKET>/ws/<WSID>/concluido/*",
"arn:aws:s3:::<BUCKET>/ws/<WSID>/erro/*",
"arn:aws:s3:::<BUCKET>/ws/<WSID>/recibo/*"
]
},
{
"Sid": "ListarORetornoDaZabit",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::<BUCKET>",
"Condition": {
"StringLike": {
"s3:prefix": [
"ws/<WSID>/concluido/*",
"ws/<WSID>/erro/*",
"ws/<WSID>/recibo/*"
]
}
}
}
A leitura vale só para concluido/, erro/ e recibo/ da sua pasta. Não há leitura de entrada/,
processando/ ou quarentena/, nem permissão para apagar nada.
2. O que você nos informa e o que recebe
| Você nos informa | A Zabit informa a você |
|---|---|
| O ID da sua conta AWS (12 dígitos). | <BUCKET>: o nome do bucket de recepção. |
O ARN da role que fará os envios, por exemplo arn:aws:iam::<ACCOUNT_ID>:role/<NOME_DA_ROLE>. | <WSID>: o identificador da sua pasta (o seu espaço de trabalho). |
<KMS_KEY_ARN>: o ARN da chave de criptografia do canal. | |
A região: sa-east-1 (São Paulo). |
A Zabit autoriza o ARN informado na política do bucket, restrito à sua pasta, e na política da chave. Se você informar só o ID da conta, sem a role, a autorização vale para a conta inteira e o recorte de quem pode enviar passa a depender só das policies IAM do seu lado. Por isso pedimos o ARN da role.
Enquanto o canal não está habilitado para o seu espaço de trabalho, o arquivo é recusado com
canal.desabilitado. Depois da habilitação, basta reenviar o mesmo arquivo.
3. Como enviar
Com as credenciais da sua role:
aws s3api put-object \
--region sa-east-1 \
--bucket <BUCKET> \
--key ws/<WSID>/entrada/diario-20261028.zip \
--body diario-20261028.zip \
--server-side-encryption aws:kms \
--ssekms-key-id <KMS_KEY_ARN> \
--checksum-algorithm SHA256
| Parâmetro | Situação | O que acontece sem ele |
|---|---|---|
| HTTPS (TLS) | Obrigatório | O bucket recusa qualquer requisição sem TLS. O AWS CLI e os SDKs usam TLS por padrão. |
--server-side-encryption aws:kms | Obrigatório | AccessDenied. Envio sem criptografia ou com SSE-S3 (AES256) é recusado. |
--ssekms-key-id <KMS_KEY_ARN> | Obrigatório | AccessDenied. A política do bucket exige a chave do canal. A chave padrão da conta ou qualquer outra chave é recusada. |
--key ws/<WSID>/entrada/<arquivo> | Obrigatório | AccessDenied fora da sua pasta entrada/. Grave direto em entrada/, sem subpastas. |
--checksum-algorithm SHA256 | Recomendado | O S3 calcula e confere o SHA-256 no recebimento e recusa o envio se o conteúdo chegou corrompido. A Zabit confere o mesmo valor de novo antes de processar (integridade.checksum_divergente). O S3 não permite exigir o checksum por política, por isso ele não é obrigatório. |
Uma resposta 200 com ETag quer dizer que o S3 recebeu o arquivo. O resultado do processamento
vem no recibo.
O nome do bucket não tem pontos. O acesso no estilo virtual-hosted
(<BUCKET>.s3.sa-east-1.amazonaws.com), que é o padrão do AWS CLI e dos SDKs, funciona com o
certificado TLS da AWS. Não é preciso usar path-style.
Arquivos grandes (multipart)
Um único put-object vai até 5 GB. Acima disso, ou para enviar arquivos grandes mais rápido, use
aws s3 cp, que divide o envio em partes automaticamente:
aws s3 cp completo-20261028.zip s3://<BUCKET>/ws/<WSID>/entrada/completo-20261028.zip \
--region sa-east-1 \
--sse aws:kms --sse-kms-key-id <KMS_KEY_ARN> \
--checksum-algorithm SHA256
No envio em partes, o S3 confere o checksum de cada parte. A conferência do SHA-256 do arquivo inteiro pela Zabit vale para envios de uma parte só. Envios em partes interrompidos e nunca concluídos são descartados depois de 1 dia.
Com SDK
Python (boto3):
import boto3
s3 = boto3.client("s3", region_name="sa-east-1")
with open("diario-20261028.zip", "rb") as arquivo:
s3.put_object(
Bucket="<BUCKET>",
Key="ws/<WSID>/entrada/diario-20261028.zip",
Body=arquivo,
ServerSideEncryption="aws:kms",
SSEKMSKeyId="<KMS_KEY_ARN>",
ChecksumAlgorithm="SHA256",
)
Para arquivos grandes, s3.upload_file(...) faz o multipart, com os mesmos parâmetros em
ExtraArgs={"ServerSideEncryption": "aws:kms", "SSEKMSKeyId": "<KMS_KEY_ARN>", "ChecksumAlgorithm": "SHA256"}.
Node.js (AWS SDK v3):
import { readFile } from "node:fs/promises";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: "sa-east-1" });
await s3.send(
new PutObjectCommand({
Bucket: "<BUCKET>",
Key: "ws/<WSID>/entrada/diario-20261028.zip",
Body: await readFile("diario-20261028.zip"),
ServerSideEncryption: "aws:kms",
SSEKMSKeyId: "<KMS_KEY_ARN>",
ChecksumAlgorithm: "SHA256",
}),
);
Não envie ACL: o objeto passa a pertencer à Zabit no momento do envio.
4. Nome do arquivo e parâmetros do lote
No upload pelo portal, uma pessoa escolhe o tipo do processamento, a data e as opções. No bucket não há ninguém para escolher; por isso a Zabit decide os parâmetros do lote campo a campo, nesta ordem (o primeiro que informar o campo vale):
- o sidecar
.manifest.jsonenviado junto do ZIP (opcional); - o nome do arquivo, pela convenção abaixo;
- o padrão do seu espaço de trabalho, combinado na habilitação;
- se nada decidiu e a escolha seria ambígua, a Zabit não adivinha: recusa e diz o motivo no recibo.
Convenção de nome
{tipo}-AAAAMMDD[-n].zip
tipo:completooudiario, em letras minúsculas;AAAAMMDD: a data de referência do movimento;-n: contador opcional, de 1 a 3 dígitos, para mais de um arquivo no mesmo dia.
Exemplos: completo-20261028.zip, diario-20261028.zip, diario-20261028-2.zip.
Um nome fora dessa forma não é erro: ele só não informa nada, e o tipo vem do sidecar ou do padrão do
espaço de trabalho. Já um nome na forma certa com data inexistente (diario-20261340.zip) é recusado
com parametro.data_invalida.
Sidecar .manifest.json (opcional)
Para escolher os parâmetros explicitamente, envie um arquivo com o mesmo nome do ZIP mais
.manifest.json, também em entrada/ e com os mesmos parâmetros de criptografia:
ws/<WSID>/entrada/diario-20261028.zip.manifest.json
Envie o sidecar antes do ZIP. A Zabit lê o sidecar no momento em que admite o ZIP; se o ZIP chegar antes, ele pode ser admitido sem o sidecar.
{
"process_type": "diario",
"data_referencia": "2026-10-28",
"routine_code": "<CODIGO_DA_ROTINA>",
"reporting_entity_id": "<ID_DA_INSTITUICAO>",
"livro_edicao_id": "<ID_DA_EDICAO>",
"max_alerts_per_run": 5000
}
| Campo | Valor aceito | Se ausente |
|---|---|---|
process_type | completo ou diario | vem do nome; se o espaço de trabalho só libera um tipo, vale esse; senão, parametro.tipo_ausente |
data_referencia | data AAAA-MM-DD | vem do nome; sem nome na convenção, é obtida do conteúdo do arquivo |
routine_code | código de uma rotina de ingestão ativa | a rotina padrão do espaço de trabalho; com mais de uma ativa e nenhuma padrão, admissao.rotina_ambigua |
reporting_entity_id | identificador de uma instituição ativa do espaço de trabalho | a padrão; com mais de uma ativa e nenhuma padrão, admissao.entidade_ambigua |
livro_edicao_id | identificador de uma edição do Livro de Regras do espaço de trabalho | a padrão, ou a edição vigente |
max_alerts_per_run | inteiro maior que zero | o padrão do espaço de trabalho, ou o limite geral |
- Todos os campos são opcionais;
nullequivale a não informar. - O arquivo tem no máximo 16 KiB e é um objeto JSON em UTF-8. Textos não podem ser vazios nem passar de 100 caracteres.
- Campo desconhecido, tipo de valor errado ou JSON ilegível recusam o lote
(
parametro.sidecar_invalido). Quem escreve um sidecar quer controle total, e ignorar parte dele seria adivinhar. - No caso comum (uma instituição e uma rotina), nenhum sidecar é necessário.
Um lote por vez
Envie um ZIP por vez por espaço de trabalho: espere o recibo final do anterior (concluido,
erro ou duplicado) antes de gravar o próximo. Um ZIP que chega enquanto outro do mesmo espaço de
trabalho ainda está sendo processado pode ser recusado com processamento.falhou. Nesse caso,
reenvie quando o anterior terminar.
5. Pastas de retorno e recibo
Tudo fica sob a sua pasta ws/<WSID>/. O arquivo muda de pasta conforme o estado:
ws/<WSID>/entrada/<arquivo> ← você só escreve aqui
ws/<WSID>/processando/<id>/<arquivo> ← durante o processamento (só a Zabit lê)
ws/<WSID>/concluido/AAAA/MM/<id>/<arquivo> ← terminou com sucesso, ou era duplicado
ws/<WSID>/erro/AAAA/MM/<id>/<arquivo> ← recusado ou falhou
ws/<WSID>/quarentena/AAAA/MM/<id>/<arquivo> ← o antivírus encontrou ameaça (só a Zabit lê)
ws/<WSID>/recibo/AAAA/MM/<arquivo>.json ← o recibo, para todo arquivo
AAAA/MM é o mês (UTC) em que a Zabit recebeu o arquivo; <id> é o identificador do envio. Enquanto
o antivírus examina o arquivo, ele continua em entrada/. Depois disso, todo arquivo admitido sai de
entrada/.
O recibo
Um JSON pequeno, gravado para todo arquivo, inclusive os bem-sucedidos. Ele é reescrito a cada
mudança de estado; a versão final é a que tem estado igual a concluido, erro ou duplicado.
{
"versao": 1,
"arquivo": "ws/<WSID>/entrada/diario-20261028.zip",
"etag": "\"9b2cf535f27731c974343645a3985328\"",
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"tamanho": 184372123,
"recebido_em": "2026-10-28T19:02:11Z",
"ordem": "2026-10-28T19:02:11.482915Z",
"atualizado_em": "2026-10-28T19:05:40Z",
"estado": "concluido",
"objeto_id": "0b6f1c1e-3f6e-4a53-9f43-2c1f1d5f9a10",
"execution_id": "7c1d0b56-0f0e-4a53-9f43-2c1f1d5f9a10",
"destino": "ws/<WSID>/concluido/2026/10/0b6f1c1e-3f6e-4a53-9f43-2c1f1d5f9a10/diario-20261028.zip",
"motivo": null,
"contagens": { "registros": 1200, "alertas": 7, "casos": 2 },
"proximo_passo": null,
"antivirus": "limpo"
}
Um recibo de recusa traz o motivo e o proximo_passo (trecho):
{
"estado": "erro",
"motivo": {
"codigo": "parametro.tipo_ausente",
"detalhe": "o tipo de processamento não foi indicado: o espaço de trabalho aceita completo e diario"
},
"proximo_passo": "nomear o arquivo completo-AAAAMMDD.zip ou diario-AAAAMMDD.zip, ou indicar process_type no .manifest.json, e reenviar"
}
| Campo | Significado |
|---|---|
estado | recebido, processando, concluido, erro ou duplicado (veja Estados) |
motivo.codigo | código estável da recusa ou do duplicado. Programe sobre ele. Lista em Códigos de motivo |
motivo.detalhe | explicação curta para leitura humana. Pode mudar: não programe sobre ela |
proximo_passo | o que fazer, em uma frase |
contagens | no concluido: registros, alertas e casos do lote |
antivirus | o resultado da verificação (veja Antivírus) |
arquivo / destino | onde você gravou o arquivo / onde ele está agora |
objeto_id | o identificador do envio |
execution_id | o identificador do processamento; informe-o ao suporte se precisar de ajuda |
sha256, etag, tamanho | a identificação do que foi recebido |
recebido_em, atualizado_em, ordem | horários em UTC; ordem tem microssegundos e serve para ordenar envios |
O recibo não traz dado pessoal: CPF, CNPJ e sequências longas de dígitos são mascarados no detalhe.
Se você enviar de novo um arquivo com o mesmo nome, o recibo mais recente fica em <arquivo>.json e o
anterior é preservado como <arquivo>.<8 caracteres do id>.json. Nenhum recibo é sobrescrito sem
cópia.
6. Estados
O mesmo vocabulário vale para o recibo, os webhooks e a API de status:
| Estado | Quando | Onde o arquivo está | Evento (webhook) |
|---|---|---|---|
recebido | o envio foi registrado e está na fila, ou esperando o antivírus | entrada/ ou processando/ | — |
processando | o processamento está em andamento | processando/ | — |
concluido | terminou com sucesso | concluido/ | monitoring.batch.processado |
erro | recusado ou falhou; o motivo diz por quê | erro/ (ou quarentena/, se o antivírus encontrou ameaça) | monitoring.batch.rejeitado |
duplicado | o mesmo conteúdo já foi processado ou está em processamento. Não é erro. | concluido/ | monitoring.batch.duplicado |
7. Reenvio e idempotência
Reenviar é seguro. Se o envio ficou na dúvida (timeout, queda de rede, recibo que não apareceu), reenvie. A Zabit nunca processa duas vezes o mesmo conteúdo.
A deduplicação é pelo conteúdo, com o SHA-256 do arquivo, e não pelo nome nem pelo ETag. Com a
criptografia SSE-KMS, o S3 gera um ETag diferente a cada envio, mesmo para bytes idênticos. Por isso
cada envio é um envio novo, com recibo próprio, e é o SHA-256 que decide:
- se já existe no seu espaço de trabalho um processamento do mesmo conteúdo, concluído ou em andamento,
o novo envio termina em
duplicadocomduplicado.conteudo_ja_processado, sem processar de novo. Oexecution_iddo recibo aponta para o processamento original. Isso vale para qualquer nome de arquivo; - processamentos que falharam ou foram cancelados não contam: reenviar o mesmo conteúdo depois deles processa de novo.
Quando o arquivo foi recusado, o reenvio é avaliado do começo:
- se a causa não está no arquivo (
parametro.*,admissao.entidade_ambigua,admissao.entidade_ausente,admissao.rotina_ambigua,canal.desabilitado,workspace.padrao_invalido,processamento.*,admissao.falha_persistente,objeto.*,integridade.checksum_divergente,antivirus.indisponivel): corrija o sidecar, ou aguarde o suporte, e reenvie o mesmo arquivo; - se o defeito está no arquivo (
arquivo.*,admissao.recusada,antivirus.nao_suportado,antivirus.ameaca_detectada): corrija o arquivo e reenvie. O mesmo arquivo sem correção é recusado de novo pelo mesmo motivo; com ameaça, volta para a quarentena.
Você pode reenviar com o mesmo nome ou com outro. O proximo_passo de alguns recibos sugere nome novo
para facilitar a conciliação, mas isso não é exigido: o recibo anterior é sempre preservado
(O recibo).
O que fazer em cada código está em Códigos de motivo.
Não regrave um arquivo logo depois de enviá-lo. Se ele mudar antes de ser admitido, o envio é recusado
com objeto.mudou.
8. Limites
| Limite | Valor |
|---|---|
| Formato | um ZIP não vazio, gravado direto em entrada/ |
| Conteúdo do ZIP | só arquivos .csv e .txt (veja Formato do arquivo) |
| ZIP dentro de ZIP | não aceito: o lote é recusado com arquivo.membro_nao_permitido |
| ZIP com senha | não aceito (o antivírus não consegue examinar: antivirus.nao_suportado) |
| Arquivos dentro do ZIP | até 200 |
| Pastas dentro do ZIP | até 3 níveis; nome de cada arquivo com até 255 caracteres |
| Tamanho descompactado | até 80 GiB no total e 48 GiB por arquivo |
| Taxa de compressão | até 200:1 por arquivo e 150:1 no total (acima de 256 MiB descompactados) |
| Tamanho do ZIP enviado | sem limite próprio do canal; acima de 5 GB, envio em partes (seção 3) |
| Sidecar | até 16 KiB |
| Início do processamento | até 5 minutos depois do envio. Nesse intervalo o arquivo é detectado, ganha o recibo recebido e passa pelo antivírus. O tempo até o desfecho depende do tamanho do lote |
Arquivos de sistema que o macOS acrescenta ao ZIP (__MACOSX/, ._*, .DS_Store) são ignorados,
não recusados. Se um lote legítimo passa dos limites, divida-o em ZIPs menores ou fale com o suporte.
9. Antivírus
Todo arquivo recebido pelo canal é examinado por antivírus antes de qualquer processamento, com o serviço de detecção de malware gerenciado da AWS (Amazon GuardDuty Malware Protection). O arquivo só é aberto pela Zabit depois do veredito.
- Enquanto a verificação não termina, o arquivo fica em
entrada/e o recibo dizestado: recebidocomantivirus: em_verificacao. - Ameaça encontrada: o arquivo vai para
quarentena/, não é processado e o recibo dizestado: errocomantivirus.ameaca_detectada. A quarentena não é legível por você e é apagada automaticamente depois de 30 dias. Reenviar o mesmo arquivo leva ao mesmo resultado. - Arquivo que não pode ser examinado (ZIP com senha ou compressão extrema): recusado com
antivirus.nao_suportado. A regra é recusar o que não foi examinado, nunca processar sem exame. - Verificação indisponível: a Zabit tenta de novo; se não houver veredito depois de algumas
tentativas (até 6 horas), o arquivo é recusado com
antivirus.indisponivele você pode reenviar o mesmo arquivo.
O campo antivirus do recibo:
| Valor | Significado |
|---|---|
em_verificacao | o exame está em andamento |
limpo | nenhuma ameaça encontrada |
ameaca_detectada | ameaça encontrada; o arquivo está em quarentena |
nao_suportado | o arquivo não pôde ser examinado |
indisponivel | a verificação não respondeu a tempo |
nao_escaneado | o recibo não registra veredito: o envio foi encerrado por outro motivo (por exemplo, duplicado, ZIP inválido ou parâmetro) antes do processamento |
10. Retenção
Cada pasta tem um prazo, aplicado automaticamente por regras de ciclo de vida do bucket:
| Pasta | Por quanto tempo fica |
|---|---|
entrada/ | até a admissão (até 5 minutos) |
processando/ | durante o processamento |
concluido/ | 365 dias por padrão; o prazo é configurável por cliente na habilitação (mínimo de 60 dias) |
erro/ | 90 dias |
recibo/ | 365 dias |
quarentena/ | 30 dias |
| envios em partes não concluídos | 1 dia |
Ao fim do prazo o arquivo é apagado. O bucket é versionado para proteger contra exclusão acidental, e a versão anterior de um arquivo apagado é eliminada definitivamente 14 dias depois. Esses prazos são do arquivo enviado. O tratamento dos dados processados está na Central de confiança.
11. Problemas no envio (antes do recibo)
Se o comando de envio falha, o arquivo não chegou à Zabit e não haverá recibo:
| Sintoma | Causa provável |
|---|---|
AccessDenied no envio | faltou --server-side-encryption aws:kms; a chave informada não é <KMS_KEY_ARN>; a chave do objeto está fora de ws/<WSID>/entrada/; ou a sua role ainda não foi autorizada |
AccessDenied mencionando kms: | a policy da sua role não tem kms:GenerateDataKey (ou kms:Decrypt, no envio em partes) na chave do canal, ou o ARN da chave está errado |
BadDigest ou erro de checksum | o arquivo mudou ou se corrompeu durante o envio; envie de novo |
| o envio funcionou e o recibo não aparece | aguarde até 5 minutos; passado esse prazo, fale com o suporte informando o nome do arquivo e o horário |
AccessDenied ao ler ou listar o retorno | a listagem precisa de --prefix dentro de concluido/, erro/ ou recibo/ da sua pasta; ou faltou kms:Decrypt |