SAFTCheck
← Blog
2026-05-02 · 4 min de leitura

Como corrigir o erro de codificação UTF-8 do SAF-T em Portugal

Porque a AT rejeita silenciosamente ficheiros SAF-T XML declarados como UTF-8 e a correção de uma linha que faz com que passem.

O erro que não consegue ver

Submete o seu SAF-T XML mensal à AT, o portal mostra "ficheiro inválido" e nada mais. Sem número de linha. Sem razão. O conselho habitual é voltar a exportar do ERP e submeter outra vez. Faz isso. Continua a falhar.

Se o seu ERP for Xero, QuickBooks, Datev ou uma exportação SAP genérica, é quase de certeza o mesmo bug: o seu ficheiro está codificado em UTF-8, mas a AT só aceita Windows-1252.

Porque é que a AT rejeita UTF-8 em silêncio

A especificação SAF-T (PT) da AT, esquema atual 1.04_01, exige Windows-1252 (CP-1252) como codificação do ficheiro. O validador da AT não devolve um erro útil quando a codificação está errada — o ficheiro é simplesmente rejeitado à entrada. O XML está bem formado; o esquema passa nas verificações. Mas a codificação ao nível dos bytes dos caracteres acentuados (ç, ã, é, ó) não corresponde ao que as ferramentas a jusante da AT esperam.

Os ERPs estrangeiros usam UTF-8 por defeito porque UTF-8 é o padrão moderno em todo o lado, exceto neste pipeline fiscal português específico. Eles escrevem o prólogo XML assim:

<?xml version="1.0" encoding="UTF-8"?>

A AT espera:

<?xml version="1.0" encoding="Windows-1252"?>

Porque é que mexer só na declaração não resolve

O primeiro instinto é abrir o ficheiro, substituir UTF-8 por Windows-1252 no prólogo, gravar e voltar a submeter. Isso piora as coisas. A declaração agora diz Windows-1252 mas os bytes continuam a ser UTF-8. Qualquer caractere fora de ASCII fica em garbage quando a AT ou qualquer outra pessoa lê o ficheiro com a codificação declarada.

A correção que funciona mesmo

Precisa de fazer duas coisas, pela ordem certa:

  1. Transcodifique os bytes do corpo de UTF-8 para Windows-1252. Cada Á (0xC3 0x81 em UTF-8) passa a um único byte 0xC1. Cada ç (0xC3 0xA7) passa a 0xE7. E por aí adiante.
  2. Reescreva a declaração de codificação para corresponder: encoding="Windows-1252".

Se saltar o passo 1, fica com garbage. Se saltar o passo 2, a AT continua a rejeitar.

Fazê-lo em PHP

$xml = file_get_contents('saft.xml');
$converted = mb_convert_encoding($xml, 'Windows-1252', 'UTF-8');
$converted = preg_replace(
    '/(<\?xml[^>]*encoding=)["\'][^"\']+["\']/i',
    '$1"Windows-1252"',
    $converted,
    1,
);
file_put_contents('saft.fixed.xml', $converted);

Fazê-lo num clique

O SAFTCheck faz exatamente esta transformação, mais retira quaisquer bytes BOM UTF-8 (outra causa de rejeição silenciosa), valida o ficheiro corrigido contra o esquema AT 1.04_01 e devolve um XML limpo para download. Grátis para uma correção; €7 para a correção paga com relatório completo e PDF.

Como confirmar que a correção pegou

Depois da conversão, corra file saft.fixed.xml num terminal Unix — deve devolver "Non-ISO extended-ASCII text" ou "ISO-8859 text", e não "UTF-8 Unicode". Abra o ficheiro num editor hexadecimal: qualquer caractere português acentuado deve ser um único byte na gama 0xA0–0xFF, e não uma sequência de dois bytes com prefixo 0xC2/0xC3.

Submeta o ficheiro à AT outra vez. O "ficheiro inválido" deve desaparecer. Se não desaparecer, a codificação não era o único problema — mas geralmente é.

Regras relacionadas que o SAFTCheck apanha ao mesmo tempo

  • BOM no início do ficheiro. A AT rejeita BOM UTF-8 (0xEF 0xBB 0xBF) e BOMs UTF-16.
  • Dígito de controlo do NIF. Algoritmo Mod-11; um dígito errado e o ficheiro é inválido.
  • Formato ATCUD. Obrigatório desde 2023, tem de corresponder a ^[A-Z0-9]{8}-[1-9]\d{0,9}$.
  • Datas do cabeçalho. StartDate antes de EndDate, FiscalYear corresponde ao ano de StartDate, EndDate não está no futuro.

Outros artigos

Usamos um cookie de sessão para autenticação, um cookie de idioma para a preferência linguística e o Tawk.to para o widget de chat ao vivo (que define os seus próprios cookies quando abre o chat). O Google Analytics (GA4) só é carregado depois de aceitar, com anonimização de IP; sem rastreadores publicitários. Consulte a nossa Política de Privacidade.