---
title: "Caracteres especiais nos campos de texto da NFS-e"
description: "Como a plataforma trata acentos, emoji, aspas curvas, travessões e caracteres invisíveis nos campos de texto da NFS-e, o que cada provedor municipal aceita e como corrigir uma rejeição."
source_url: https://nfe.io/docs/documentacao/nota-fiscal-servico-eletronica/duvidas/erros-e-status/caracteres-especiais-nfse
last_updated: 2026-09-18
---

# Caracteres especiais nos campos de texto da NFS-e

> 🇺🇸 This page is also available in English: [Special characters in NFS-e text fields](./caracteres-especiais-en.md).

Campos de texto livre da NFS-e — descrição do serviço, informações complementares, razão social e endereço do tomador — aceitam conjuntos de caracteres **diferentes conforme o município de emissão**. Um mesmo texto pode ser aceito num município e recusado em outro.

Esta página descreve como a plataforma trata cada caso, para você saber **o que enviar** e **o que fazer quando uma nota é recusada**.

## Em uma frase

Se todos os seus textos usarem apenas **letras, números, pontuação comum e acentos do português** (o conjunto **Latin-1**, de `U+0000` a `U+00FF`), eles são aceitos em **todos** os provedores. Os problemas vêm de caracteres que parecem inofensivos mas estão fora desse conjunto: travessão `–`, aspas curvas `" "`, reticências `…`, bullet `•`, emoji e caracteres invisíveis.

:::tip Regra prática
Acento é seguro. Aspas curvas, travessão e emoji não são.
:::

## As três camadas de validação

Um texto enviado na emissão passa por três verificações independentes, nesta ordem:

| Ordem | Camada | O que faz | Quando falha |
|---|---|---|---|
| 1 | **API** | Recusa o que é impossível representar em documento fiscal eletrônico | `HTTP 400` na hora da requisição |
| 2 | **Plataforma** | Normaliza o texto para o formato que o provedor do município exige | — (ajuste silencioso, quando é seguro) |
| 3 | **Provedor municipal** | Valida contra o schema da prefeitura | Nota recusada, com mensagem no webhook |

---

## Camada 1 — Validação na API

Acontece no momento da requisição, antes de qualquer processamento.

### Corpo precisa estar em UTF-8

O JSON deve ser enviado codificado em **UTF-8**. Corpo em outra codificação é recusado com `HTTP 400`:

```json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "$": ["O corpo da requisicao nao esta em UTF-8. Reenvie o JSON codificado em UTF-8."]
  },
  "traceId": "00-1f3a…-b7c2…-01"
}
```

A resposta segue o formato `application/problem+json`, o mesmo dos demais erros de validação da API. Guarde o `traceId`: é por ele que o suporte localiza a requisição.

:::warning Causa mais comum
Texto originado em sistemas legados que usam **Windows-1252** e é enviado sem conversão. Acentos viram bytes inválidos no meio do caminho.
:::

### Caracteres impossíveis em XML são recusados

A NFS-e é transmitida à prefeitura como XML. Alguns caracteres **não existem** em XML 1.0 — o principal é o caractere nulo `U+0000`, mas a regra vale para todos os caracteres de controle proibidos pelo padrão.

Quando o corpo contém um deles, a resposta é `HTTP 400` nomeando **a propriedade e o code point**:

```json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "description": [
      "Contém o caractere U+0000, inválido em XML 1.0 e não permitido em documento fiscal eletrônico. Remova o caractere e reenvie a requisição."
    ]
  },
  "traceId": "00-1f3a…-b7c2…-01"
}
```

Até **10 ocorrências distintas** são reportadas de uma vez, para você corrigir tudo numa passada em vez de descobrir um campo por tentativa.

:::note Quando a chave vem como `$`
A chave do objeto `errors` é o nome da propriedade em que o caractere apareceu. Quando ele não pertence ao valor de nenhuma propriedade — está numa chave do JSON ou num elemento solto de array —, a chave vem como `$`, que é a convenção da API para erro de corpo sem caminho de propriedade. Nomes de propriedade muito longos são truncados na resposta.
:::

:::note Por que o code point aparece como `U+XXXX`
Vários desses caracteres são **invisíveis**. Se a mensagem imprimisse o caractere em si, você leria *"contém o caractere "* sem ver caractere nenhum. O code point é o que permite localizá-lo no seu payload.
:::

### O que é ajustado em vez de recusado

Caracteres que **podem** ser representados em XML, mas exigem tratamento — como `&`, `<` e `>` — são escapados automaticamente. Nada é removido do seu texto nessa etapa.

---

## Camada 2 — Normalização pela plataforma

Quando o provedor do município exige um formato específico e a conversão é **inequívoca**, a plataforma ajusta o texto sozinha. O critério é não alterar o sentido do que você enviou.

### Transliteração para Latin-1 (padrão nacional)

Nos municípios que usam o **padrão nacional da NFS-e**, os campos de texto seguem um tipo de schema que aceita apenas `U+0020` a `U+00FF` e **não admite quebra de linha nem tabulação**. Antes do envio, a plataforma aplica:

| Entrada | Vira | Observação |
|---|---|---|
| Quebra de linha, tabulação, `CR` | espaço | Espaços consecutivos colapsam em um |
| `–` `—` `−` `•` (travessão, bullet) | `-` | Hífen comum |
| `'` `'` `′` (aspas simples curvas) | `'` | Apóstrofo reto |
| `"` `"` `″` (aspas duplas curvas) | `"` | Aspas retas |
| `…` (reticências) | `...` | Três pontos |
| Emoji, ideogramas, símbolos sem equivalente | espaço | Não há conversão possível |
| Acentos, `ç`, `ª`, `º` | *inalterado* | Latin-1 passa intacto |

O texto é aparado no início e no fim. **Nada é truncado** nesta etapa — o limite de tamanho é validado separadamente, por campo.

:::info A descrição do serviço é a exceção
No padrão nacional, o campo de **discriminação do serviço** usa um tipo de schema mais permissivo e **aceita quebras de linha**. A normalização acima não se aplica a ele. Já as **informações complementares** seguem a regra restrita.
:::

:::warning Campo que sanitiza para vazio é omitido
Se um campo contiver **apenas** caracteres sem equivalente (por exemplo, só emoji), o resultado da normalização é vazio — e o campo é **omitido** da nota, porque o schema exige ao menos um caractere. O texto não aparece no documento emitido.
:::

### Remoção de acentos

Uma parte dos provedores municipais não aceita acentuação. Para eles, a plataforma remove os sinais diacríticos automaticamente antes do envio: `São Paulo` vira `Sao Paulo`, `informações` vira `informacoes`. A letra base é preservada; nada é descartado.

### Campos de largura fixa

Alguns municípios recebem a nota em formato de texto posicional, não XML. Nesses casos, além da remoção de acentos, cada campo é **ajustado ao tamanho exato** exigido pelo layout — texto mais longo é cortado no limite do campo.

---

## Camada 3 — Validação do provedor municipal

Aqui a nota já saiu da plataforma. A recusa vem da prefeitura e chega até você pelo **webhook** e pela consulta da nota.

### Provedores que recusam fora de Latin-1

Alguns provedores **recusam a nota inteira** quando qualquer campo de texto contém caractere acima de `U+00FF`, em vez de aceitar uma conversão. O caso mais relevante é o da **Prefeitura de São Paulo (NFS-e Paulistana)**.

Nesse cenário a plataforma **não translitera** o texto: a recusa acontece **antes** do envio à prefeitura, com uma mensagem que nomeia campo, caractere e code point:

```text
[E1002] A Prefeitura de São Paulo não aceita o caractere '–' (U+2013) no campo
Discriminação dos serviços (Discriminacao). Os campos de texto admitem apenas
caracteres do conjunto Latin-1 (ISO-8859-1). Substitua por um equivalente:
travessão por hífen '-', reticências por '...', aspas curvas por aspas retas,
caractere invisível pela remoção. Depois, reenvie a nota.
```

Até **10 ocorrências distintas** são listadas por recusa.

:::info Quando a recusa não vem nesse formato
Essa verificação prévia depende da configuração do município e do provedor. Onde ela não está em vigor, a nota é enviada e quem recusa é a própria prefeitura, com um erro de schema genérico — tipicamente `[1001] XML não compatível com Schema` — que **não aponta o campo nem o caractere**. O tratamento é o mesmo: procure caracteres fora do Latin-1 nos campos de texto, começando pelos invisíveis.
:::

:::note Por que recusar em vez de corrigir sozinho
Uma recusa você resolve em segundos reenviando o texto ajustado. Um texto **alterado** dentro de uma nota já autorizada pela prefeitura **não tem volta** — o documento fiscal fica com conteúdo diferente do que você enviou. Por isso, quando a conversão não é inequívoca, a plataforma prefere devolver o erro.
:::

A regra vale para **todos** os campos de texto, não só a discriminação. Os mais afetados na prática:

- Discriminação dos serviços
- Razão social do tomador
- Logradouro, número, complemento e bairro do endereço
- E-mail do tomador e do intermediário

### Provedores que exigem apenas ASCII

Um pequeno grupo de provedores rejeita **qualquer** caractere não-ASCII, inclusive acentos. Para eles, a plataforma converte automaticamente cada caractere acentuado em uma **referência numérica** — uma representação que o provedor reconverte no caractere original ao processar a nota. O acento aparece corretamente no documento final; a conversão é transparente para você.

---

## Tabela de substituições recomendadas

Se o seu sistema gera texto a partir de editores (Word, Google Docs, navegadores) ou de conteúdo colado por usuários, estes são os caracteres que aparecem com mais frequência e que você deve substituir **na origem**:

| Caractere | Nome | Code point | Substitua por |
|---|---|---|---|
| `–` | Travessão curto (en dash) | `U+2013` | `-` |
| `—` | Travessão longo (em dash) | `U+2014` | `-` |
| `'` `'` | Aspas simples curvas | `U+2018` `U+2019` | `'` |
| `"` `"` | Aspas duplas curvas | `U+201C` `U+201D` | `"` |
| `…` | Reticências | `U+2026` | `...` |
| `•` | Marcador de lista | `U+2022` | `-` |
| `→` | Seta | `U+2192` | `->` |
| `™` `®` | Marca registrada | `U+2122` `U+00AE` | `(TM)` / `(R)` — `®` é Latin-1 e passa |
| *(invisível)* | Word joiner | `U+2060` | remover |
| *(invisível)* | Espaço de largura zero | `U+200B` | remover |
| *(invisível)* | Marca de ordenação | `U+200E` `U+200F` | remover |
| 😀 🙏 | Emoji | acima de `U+FFFF` | remover |

:::danger Caracteres invisíveis são a causa mais difícil de diagnosticar
`U+2060`, `U+200B` e similares entram no texto por cópia e colagem e **não aparecem na tela**. Uma descrição visualmente idêntica a outra que funcionou pode ser recusada por causa deles. Por isso as mensagens de erro sempre trazem o code point.
:::

---

## Como diagnosticar uma rejeição

| Sintoma | Camada | O que fazer |
|---|---|---|
| `HTTP 400` com `"não esta em UTF-8"` | API | Converta o corpo da requisição para UTF-8 |
| `HTTP 400` citando `U+XXXX` e `inválido em XML 1.0` | API | Remova o caractere indicado do campo indicado |
| Nota recusada com `[E1002]` | Provedor | Substitua os caracteres listados pelos equivalentes Latin-1 e reenvie |
| Nota recusada por erro de schema sem campo identificado (ex.: `[1001] XML não compatível com Schema`) | Provedor | Verifique quebras de linha e caracteres fora de Latin-1 nos campos de texto |
| Texto saiu **sem acento** no documento | Plataforma | Comportamento esperado para o provedor do município |
| Campo **não apareceu** no documento | Plataforma | O conteúdo era só de caracteres sem equivalente e foi omitido |

:::tip Como encontrar o caractere no seu payload
Use o code point da mensagem. Em JavaScript: `texto.codePointAt(i).toString(16)`. Em C#: `char.ConvertToUtf32(texto, i).ToString("X4")`. Em Python: `hex(ord(c))`.
:::

---

## Boas práticas para a integração

1. **Normalize na origem.** Aplique a tabela de substituições antes de enviar, não depois de uma recusa. O conjunto Latin-1 funciona em todos os provedores.
2. **Trate o texto colado por usuários.** É de onde vêm aspas curvas, travessões e invisíveis.
3. **Não dependa de quebras de linha.** Só a discriminação do serviço as aceita, e apenas em parte dos municípios. Use separadores como ` - ` ou ` | ` para estruturar o texto.
4. **Leia o webhook de falha.** As mensagens nomeiam campo, caractere e code point — são suficientes para automatizar a correção.
5. **Valide o tamanho separadamente.** A normalização de caracteres não trunca; o limite de cada campo é validado por conta própria.

## Páginas relacionadas

- [Cenários de erro 400 (BadRequest) na emissão de NFS-e](./cenarios-de-badrequest-na-emissao.md)
- [Solução de erros 400 na emissão](./solucao-de-erros-400-na-emissao.md)
- [Tipos de retorno de status das requisições HTTP](./tipos-de-retorno-de-status-das-requisicoes-https.md)
