Skip to content

Comprovante de Residência

Este endpoint recebe um comprovante de residência — conta de luz, água, telefone, internet — lê o endereço impresso nele e confere esse endereço com a base dos Correios.

A resposta separa as duas coisas com clareza:

  • extraction — o endereço que estava escrito no comprovante.
  • postOfficeData — o endereço que os Correios têm cadastrado para aquele CEP, mais um matches dizendo, campo a campo, se os dois batem.

É essa comparação que dá valor à resposta. Um CEP lido corretamente mas com logradouro divergente é um sinal diferente de um endereço que confere inteiro, e o matches é onde essa diferença aparece.

Este endpoint não verifica se o documento enviado é mesmo um comprovante de residência: ele assume que é, e tenta extrair um endereço dali. Também não confere o titular da conta contra a Receita Federal, e não lê documentos de identificação — para isso existe o Full OCR.

PDF original entrega mais que foto de conta impressa

Para uma lista de emissores conhecidos, um PDF com o texto embutido — o arquivo que você baixa do site da concessionária — é lido direto do texto do PDF, sem passar por OCR. Esse caminho é mais preciso, e é o único em que o número e o complemento do endereço são efetivamente preenchidos.

Uma foto ou um print da conta cai no caminho de OCR, que é mais sujeito a erro de leitura. Sempre que o seu fluxo permitir, peça o PDF original.

Request

POST /proof-of-residence/v3

Headers

http
Authorization: ApiKey <sua-chave-de-api>

Este endpoint também aceita o token JWT, no formato Authorization: Bearer <accessToken>. Veja Token JWT.

Parâmetros

Este endpoint não tem parâmetros de query string. Os que você enviar são ignorados, sem erro.

Arquivos aceitos

ItemValor
Formatosapplication/pdf, image/png, image/jpeg
Quantidade por chamada1 arquivo
Tamanho máximo (multipart)15 MB

O nome do campo do formulário é livre — ele volta na resposta, em data[].fieldname e em metadata.filesInfo[].fieldname. Enviar mais de um arquivo é recusado com 422.

Além do multipart/form-data, o arquivo pode ser enviado em JSON, no campo base64, como descrito em Envio de arquivos.

Exemplo Request

bash
curl -i -X POST 'https://api-homolog.nxcd.app/proof-of-residence/v3' \
  --header 'Authorization: ApiKey SUA_CHAVE_AQUI' \
  --form 'comprovante=@./conta-de-luz.pdf'

Response

Esta seção descreve a v3, a versão recomendada. A v2 e a v4 devolvem um data com outro formato — veja Versões.

O envelope é o padrão da API:

CampoDescriçãoTipo
idIdentificador único da requisiçãoString
versionVersão da API que atendeu a chamadaString
dataLista com uma entrada por endereço reconhecido. Vem vazia quando nenhum endereço foi validado.Object[]
metadataMetadados da requisiçãoObject

‼️ data vazio é resposta normal, não erro

Na v3, no caminho de OCR, um endereço cujo CEP não foi localizado na base dos Correios é descartado da resposta. Se o comprovante não rendeu nenhum CEP válido, você recebe 200 com "data": [] e metadata.filesInfo normalmente preenchido. No caminho de leitura direta do PDF esse descarte não acontece: o resultado volta com os matches em false.

Verifique o tamanho de data antes de ler data[0] — um código que assume data[0].extraction recebe um erro em vez de um resultado vazio.

Cada item de data traz:

CampoDescriçãoTipo
pagePágina do arquivo em que o endereço foi encontrado. Começa em 0.Number
fieldnameNome do campo em que o arquivo foi enviadoString

Mais os três blocos abaixo.

data[].extraction

O endereço como estava escrito no comprovante, sem nenhuma correção.

CampoDescriçãoTipo
extraction.zipCodeCEP lido, sem máscaraString
extraction.addressLogradouro lidoString
extraction.numberNúmero lidoString
extraction.districtBairro lidoString
extraction.cityCidade lidaString
extraction.stateUF lidaString
extraction.addressComplementComplemento lidoString

🚧 number e addressComplement dependem do caminho de leitura

No caminho de OCR — imagens, e PDFs de emissores não reconhecidos — esses dois campos ainda não são extraídos e voltam vazios.

Eles só são preenchidos no caminho de leitura direta do PDF, descrito no início desta página. Não construa uma regra de negócio que dependa deles sem tratar o vazio.

json
{
  "extraction": {
    "zipCode": "84145000",
    "address": "R RIO SOLIMOES",
    "number": "169",
    "district": "CENTRO",
    "city": "CARAMBEI",
    "state": "PR",
    "addressComplement": "CASA 01"
  }
}

data[].postOfficeData

O endereço que os Correios têm cadastrado para o CEP lido, e o resultado da comparação com o que estava no comprovante.

CampoDescriçãoTipo
postOfficeData.zipCodeCEP conforme os CorreiosString
postOfficeData.addressLogradouro conforme os CorreiosString
postOfficeData.districtBairro conforme os CorreiosString
postOfficeData.cityCidade conforme os CorreiosString
postOfficeData.stateUF conforme os CorreiosString
postOfficeData.matches.zipCodetrue quando o CEP lido foi localizado na base dos CorreiosBoolean
postOfficeData.matches.addresstrue quando o logradouro lido corresponde ao dos CorreiosBoolean
postOfficeData.matches.districttrue quando o bairro correspondeBoolean
postOfficeData.matches.citytrue quando a cidade correspondeBoolean
postOfficeData.matches.statetrue quando a UF correspondeBoolean

Como ler os matches

matches.zipCode é o mais importante: ele diz se o CEP existe. Os outros quatro comparam texto — a leitura do OCR contra o cadastro dos Correios — e um false neles significa "não confere", o que pode ser tanto endereço divergente quanto erro de leitura.

postOfficeData traz o endereço dos Correios, não o do comprovante. Quando você quer mostrar o endereço ao usuário e os matches conferem, o dos Correios é o mais confiável dos dois; o número e o complemento, porém, os Correios não têm — esses só existem em extraction.

json
{
  "postOfficeData": {
    "zipCode": "84145000",
    "address": "Rua Rio Solimoes",
    "district": "Centro",
    "city": "Carambei",
    "state": "PR",
    "matches": {
      "zipCode": true,
      "address": true,
      "district": true,
      "city": true,
      "state": true
    }
  }
}

data[].classification

CampoDescriçãoTipo
classification.typeTipo do documentoString
classification.subtypeEmissor reconhecido do comprovante, quando houverString
classification.countryPaís emissorString
classification.sidesLista com uma entrada por página analisadaObject[]
classification.sides[].sideFace do documento. No caminho de OCR, sempre null.String/null
classification.sides[].pagePágina analisada, começando em 0Number
classification.sides[].fieldnameNome do campo em que o arquivo foi enviadoString
classification.sides[].confidenceConfiança da classificaçãoNumber

🚧 Este bloco não é um classificador

Neste endpoint, classification é em boa parte um rótulo fixo, não o resultado de uma análise. No caminho de OCR, ele vem sempre com type: "ProofOfResidence", subtype: null, country: "BRA" e confidence: 0 — inclusive quando o arquivo enviado não é um comprovante de residência.

O subtype só traz o emissor reconhecido, e a confidence só sobe de zero, no caminho de leitura direta do PDF.

Não use este bloco para decidir se o arquivo enviado era mesmo um comprovante. Se você precisa dessa verificação, faça-a antes, com o Classificador.

metadata

CampoDescriçãoTipo
metadata.filesInfoLista com uma entrada por arquivo enviadoObject[]
metadata.filesInfo[].fieldnameNome do campo usado no envioString
metadata.filesInfo[].nameNome do arquivo enviadoString
metadata.filesInfo[].sizeTamanho do arquivo, em bytesNumber
metadata.filesInfo[].pagesNúmero de páginas do arquivo. Para imagens, 1.Number
metadata.filesInfo[].mimetypeTipo do arquivo enviadoString
metadata.filesInfo[].encodingCodificação do arquivo no envioString
metadata.filesInfo[].sha256Hash SHA-256 do arquivo enviadoString
metadata.timeSpentTempo de processamento da requisição, em milissegundosNumber

Exemplo completo

Status Code: 200

Comprovante lido e endereço conferido com os Correios:

json
{
  "id": "f9ba65a4-394e-4bbc-8748-7789a1bae137",
  "version": "v3",
  "data": [
    {
      "page": 0,
      "fieldname": "comprovante",
      "extraction": {
        "zipCode": "84145000",
        "address": "R RIO SOLIMOES",
        "number": "169",
        "district": "CENTRO",
        "city": "CARAMBEI",
        "state": "PR",
        "addressComplement": "CASA 01"
      },
      "postOfficeData": {
        "zipCode": "84145000",
        "address": "Rua Rio Solimoes",
        "district": "Centro",
        "city": "Carambei",
        "state": "PR",
        "matches": {
          "zipCode": true,
          "address": true,
          "district": true,
          "city": true,
          "state": true
        }
      },
      "classification": {
        "type": "ProofOfResidence",
        "subtype": null,
        "country": "BRA",
        "sides": [
          {
            "side": null,
            "page": 0,
            "fieldname": "comprovante",
            "confidence": 0
          }
        ]
      }
    }
  ],
  "metadata": {
    "filesInfo": [
      {
        "fieldname": "comprovante",
        "name": "conta-de-luz.pdf",
        "size": 82611,
        "pages": 1,
        "mimetype": "application/pdf",
        "encoding": "7bit",
        "sha256": "e844439599eb440b031d4ce4ebe2cfe3c37d483fb76d56b622bc77da6c435bed"
      }
    ],
    "timeSpent": 4199
  }
}

Endereço parcialmente conferido

Status Code: 200

CEP encontrado, mas o logradouro lido não corresponde ao cadastro dos Correios:

json
{
  "id": "6a1d1e8c-4a7d-4a5e-9f2c-1b0f2a9c7e11",
  "version": "v3",
  "data": [
    {
      "page": 0,
      "fieldname": "comprovante",
      "extraction": {
        "zipCode": "84145000",
        "address": "R RI0 S0LIM0ES",
        "number": "",
        "district": "CENTR0",
        "city": "CARAMBEI",
        "state": "PR",
        "addressComplement": ""
      },
      "postOfficeData": {
        "zipCode": "84145000",
        "address": "Rua Rio Solimoes",
        "district": "Centro",
        "city": "Carambei",
        "state": "PR",
        "matches": {
          "zipCode": true,
          "address": false,
          "district": false,
          "city": true,
          "state": true
        }
      },
      "classification": {
        "type": "ProofOfResidence",
        "subtype": null,
        "country": "BRA",
        "sides": [
          { "side": null, "page": 0, "fieldname": "comprovante", "confidence": 0 }
        ]
      }
    }
  ],
  "metadata": {
    "filesInfo": [
      {
        "fieldname": "comprovante",
        "name": "conta.jpg",
        "size": 421308,
        "pages": 1,
        "mimetype": "image/jpeg",
        "encoding": "7bit",
        "sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
      }
    ],
    "timeSpent": 6210
  }
}

Nenhum endereço validado

Status Code: 200
json
{
  "id": "a7f0c3d2-9e51-4b8a-8f31-2d4c6b9e0a77",
  "version": "v3",
  "data": [],
  "metadata": {
    "filesInfo": [
      {
        "fieldname": "comprovante",
        "name": "foto-borrada.jpg",
        "size": 152064,
        "pages": 1,
        "mimetype": "image/jpeg",
        "encoding": "7bit",
        "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
      }
    ],
    "timeSpent": 5030
  }
}

Headers de resposta

HeaderQuando aparece
Nextid-ReqIdEm todas as respostas. Traz o identificador da requisição, o mesmo do campo id do corpo.

Erros

CódigoQuando ocorre
401 UnauthorizedChave de API ou token JWT ausente, inválido, ou sem permissão para este endpoint.
404 Not FoundA versão informada na URL não existe. Só v2, v3 e v4 são aceitas.
406 Not AcceptableO corpo foi enviado como JSON, mas sem o campo base64.
413 Payload Too LargeO arquivo enviado em multipart/form-data passa de 15 MB.
415 Unsupported Media TypeO header Content-Type está ausente ou não é suportado.
422 Unprocessable EntityO formato do arquivo não é aceito, ou foi enviado mais de um arquivo na mesma chamada.
500 Internal Server ErrorFalha inesperada durante o processamento.

O formato das respostas de erro está descrito em Códigos HTTP das respostas.

Versões

A versão recomendada é a v3, chamada em /proof-of-residence/v3.

‼️ Omitir a versão entrega a chamada para a v2

POST /proof-of-residence, sem versão no caminho, é atendido pela v2, que tem outro formato de resposta. Informe sempre a versão na URL.

VersãoSituaçãoO que muda
v3Recomendadamatches como booleanos simples, classification com type/subtype/country/sides. Descarta endereços com CEP não localizado.
v4DisponívelFormato diferente: extraction deixa de ser o endereço e passa a trazer titular e identificadores da conta, e aparece um enhanced. Veja abaixo.
v2Legadamatches como objetos com matched, confidence e strategy. classification com outro vocabulário. Não descarta nada. É o que responde quando a versão é omitida na URL.

O que muda na v4

A v4 muda o propósito do extraction. Em vez do endereço, ele passa a trazer quem é o titular e quais são os identificadores da conta — é a versão para quem precisa casar o comprovante com um cliente, e não só validar o endereço.

CampoDescriçãoTipo
extraction.nameNome do titular, como lido na contaString
extraction.addressBloco de endereço, como lido na contaString
extraction.taxNumberCPF ou CNPJ do titular, como lido na contaString
extraction.clientNumberNúmero do cliente na concessionáriaString
extraction.installationNúmero da instalação ou da unidade consumidoraString
enhancedOs mesmos quatro identificadores já normalizados, somados aos campos de endereço separados (zipCode, address, number, district, city, state, complement)Object
postOfficeDataMesmo formato da v3Object
classificationO emissor reconhecido do comprovante, com name, uf e version — não o type/subtype/country da v3Object

🚧 Na v4 o complemento se chama complement, e não addressComplement

Nos campos de endereço da v2 e da v3, o complemento vem em addressComplement. No enhanced da v4, o mesmo dado vem em complement. É isso mesmo que a API devolve — os dois nomes convivem, cada um na sua versão.

🚧 A v4 não tem formato único

O conteúdo da v4 depende do caminho que a análise tomou. Na leitura direta do PDF, o extraction volta com os campos de endereço da v3, e enhanced e classification não aparecem. No caminho de OCR, vale a tabela acima.

Além disso, a v4 não descarta endereços com CEP não localizado, ao contrário da v3.

Se o que você precisa é validar endereço, fique na v3. Considere a v4 quando precisar do titular e dos identificadores da conta, e trate a ausência dos campos.

‼️ Trocar de versão quebra quem lê a resposta

O resultado da conferência do CEP está em data[].postOfficeData.matches.zipCode.matched na v2 e em data[].postOfficeData.matches.zipCode na v3 — booleano direto. E o endereço lido do comprovante, que na v2 e na v3 está em data[].extraction, na v4 muda de significado.

Um código escrito para uma versão não funciona na outra sem alteração.

O que muda na v2

Duas diferenças em relação à v3:

  • postOfficeData.matches é mais detalhado. Cada campo vira um objeto com matched, confidence (0 a 1) e strategy (char-to-char ou vazio), em vez de um booleano. O caminho para o mesmo dado deixa de ser matches.city e passa a ser matches.city.matched.
  • classification usa o vocabulário antigo. Traz apenas confidence e type: "PROOF-OF-RESIDENCE" — não há subtype, country nem sides.

A v2 também não descarta entradas com CEP não localizado: elas continuam em data, com os matches em false.

Status Code: 200
json
{
  "id": "13cfec7c-b238-4820-a4d1-5173e4c1418e",
  "version": "v2",
  "data": [
    {
      "page": 0,
      "fieldname": "comprovante",
      "extraction": {
        "zipCode": "84145000",
        "address": "R RIO SOLIMOES",
        "number": "",
        "district": "CENTRO",
        "city": "CARAMBEI",
        "state": "PR",
        "addressComplement": ""
      },
      "postOfficeData": {
        "zipCode": "84145000",
        "address": "Rua Rio Solimoes",
        "district": "Centro",
        "city": "Carambei",
        "state": "PR",
        "matches": {
          "zipCode": { "matched": true, "confidence": 1, "strategy": "" },
          "address": { "matched": true, "confidence": 0.94, "strategy": "char-to-char" },
          "district": { "matched": true, "confidence": 1, "strategy": "char-to-char" },
          "city": { "matched": true, "confidence": 1, "strategy": "char-to-char" },
          "state": { "matched": true, "confidence": 1, "strategy": "char-to-char" }
        }
      },
      "classification": {
        "confidence": 0,
        "type": "PROOF-OF-RESIDENCE"
      }
    }
  ],
  "metadata": {
    "filesInfo": [
      {
        "fieldname": "comprovante",
        "name": "conta-de-luz.pdf",
        "size": 82611,
        "pages": 1,
        "mimetype": "application/pdf",
        "encoding": "7bit",
        "sha256": "e844439599eb440b031d4ce4ebe2cfe3c37d483fb76d56b622bc77da6c435bed"
      }
    ],
    "timeSpent": 4199
  }
}

Nextcode | Soluções em Verificação de Identidade