Alphanumeric CNPJ: validate the check digits and look it up via API

Since July 31, 2026, Receita Federal has been issuing CNPJs with letters and numbers. The first one was a Banco do Brasil branch, 00.000.000/E08G-12. The new format is being rolled out gradually during 2026: new CNPJs may still be numeric only, and existing CNPJs remain valid, with no replacement. Sources: Receita Federal news release and Q&A on the alphanumeric CNPJ (in Portuguese).

For integrations, what changes is validation: a system that treats the CNPJ as a number, or that only accepts digits in the field, starts rejecting real companies. This guide covers the format, the regular expression, the check digit calculation with ready-to-use code, the database changes and the lookup through the CNPJAPI API, which already accepts the new format.

What changes in the format

The CNPJ still has 14 characters and the same mask. The difference is that the first 12 positions can contain uppercase letters, from A to Z:

Positions What it is Can contain letters?
1 to 8 Root (identifies the company) Yes
9 to 12 Branch number (head office or branch) Yes
13 and 14 Check digits No, always numbers

With the mask, the sample 12ABC34501DE35 becomes 12.ABC.345/01DE-35.

Regular expression

To check only the format, before calculating the check digits:

  • Without the mask: ^[A-Z0-9]{12}[0-9]{2}$
  • With the mask: ^[A-Z0-9]{2}\.[A-Z0-9]{3}\.[A-Z0-9]{3}\/[A-Z0-9]{4}-[0-9]{2}$

Both accept uppercase only: convert the input first or replace [A-Z0-9] with [A-Za-z0-9]. And a regular expression alone is not enough, because it accepts any 14-character combination in the right format. Always validate the check digits.

How to calculate the check digits

The calculation is the same modulo 11 as always. The only change is the value of each character: it is worth its ASCII code minus 48. For numbers, that gives the number itself (0 is 0, 9 is 9). For letters, A is 17, B is 18, and so on up to Z, which is 42. That is why a numeric-only CNPJ keeps the same check digits as before.

  1. Multiply each of the first 12 characters by a weight. Weights go from 2 to 9, right to left, and start over at 2 after 9. Left to right, they are 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2.
  2. Add up the results and take the remainder of the division by 11.
  3. If the remainder is 0 or 1, the digit is 0. Otherwise, the digit is 11 minus the remainder.
  4. For the second digit, repeat with the first 13 characters (the 12 plus the first check digit), with weights 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2.

For the sample 12ABC34501DE, the sum is 459, the remainder is 8 and the first digit is 3. With 12ABC34501DE3, the sum is 424, the remainder is 6 and the second digit is 5: 12ABC34501DE35.

Every version below accepts the CNPJ with or without the mask and in lowercase, rejects characters outside A to Z and 0 to 9 (including accented letters and other Unicode characters), rejects repeated sequences (such as 00000000000000) and validates both digits.

Python

import re


def calcular_dv(base: str) -> str:
    soma, peso = 0, 2
    for c in reversed(base):
        soma += (ord(c) - 48) * peso
        peso = 2 if peso == 9 else peso + 1
    resto = soma % 11
    return "0" if resto < 2 else str(11 - resto)


def cnpj_valido(entrada: str) -> bool:
    cnpj = re.sub(r"[.\-/\s]", "", entrada, flags=re.ASCII)
    if not re.fullmatch(r"[A-Za-z0-9]{12}[0-9]{2}", cnpj):
        return False
    cnpj = cnpj.upper()
    if len(set(cnpj)) == 1:
        return False
    return calcular_dv(cnpj[:12]) == cnpj[12] and calcular_dv(cnpj[:13]) == cnpj[13]


print(cnpj_valido("12.ABC.345/01DE-35"))  # True

To generate a test CNPJ, pick 12 random characters from A to Z and 0 to 9 and append the two digits: base + calcular_dv(base) gives the first 13, and calcular_dv of those 13 gives the last one.

JavaScript (Node.js and browser)

function calcularDv(base) {
  let soma = 0;
  let peso = 2;
  for (let i = base.length - 1; i >= 0; i--) {
    soma += (base.charCodeAt(i) - 48) * peso;
    peso = peso === 9 ? 2 : peso + 1;
  }
  const resto = soma % 11;
  return resto < 2 ? "0" : String(11 - resto);
}

function cnpjValido(entrada) {
  let cnpj = entrada.replace(/[.\-/ \t\r\n]/g, "");
  if (!/^[A-Za-z0-9]{12}[0-9]{2}$/.test(cnpj)) return false;
  cnpj = cnpj.toUpperCase();
  if (new Set(cnpj).size === 1) return false;
  return calcularDv(cnpj.slice(0, 12)) === cnpj[12] && calcularDv(cnpj.slice(0, 13)) === cnpj[13];
}

PHP

function calcularDv(string $base): string
{
    $soma = 0;
    $peso = 2;
    for ($i = strlen($base) - 1; $i >= 0; $i--) {
        $soma += (ord($base[$i]) - 48) * $peso;
        $peso = $peso === 9 ? 2 : $peso + 1;
    }
    $resto = $soma % 11;
    return $resto < 2 ? '0' : (string) (11 - $resto);
}

function cnpjValido(string $entrada): bool
{
    $cnpj = preg_replace('/[.\-\/\s]/', '', $entrada);
    if (!preg_match('/^[A-Za-z0-9]{12}[0-9]{2}$/', $cnpj)) {
        return false;
    }
    $cnpj = strtoupper($cnpj);
    if (count(array_unique(str_split($cnpj))) === 1) {
        return false;
    }
    return calcularDv(substr($cnpj, 0, 12)) === $cnpj[12] && calcularDv(substr($cnpj, 0, 13)) === $cnpj[13];
}

C# / .NET

using System.Linq;
using System.Text.RegularExpressions;

public static class Cnpj
{
    public static char CalcularDv(string baseCnpj)
    {
        int soma = 0, peso = 2;
        for (var i = baseCnpj.Length - 1; i >= 0; i--)
        {
            soma += (baseCnpj[i] - 48) * peso;
            peso = peso == 9 ? 2 : peso + 1;
        }
        var resto = soma % 11;
        return resto < 2 ? '0' : (char)('0' + (11 - resto));
    }

    public static bool Valido(string entrada)
    {
        var cnpj = Regex.Replace(entrada, @"[.\-/ \t\r\n]", "");
        if (!Regex.IsMatch(cnpj, @"^[A-Za-z0-9]{12}[0-9]{2}\z"))
            return false;
        cnpj = cnpj.ToUpperInvariant();
        if (cnpj.Distinct().Count() == 1)
            return false;
        return CalcularDv(cnpj[..12]) == cnpj[12] && CalcularDv(cnpj[..13]) == cnpj[13];
    }
}

Go

package cnpj

import (
	"regexp"
	"strconv"
	"strings"
)

var (
	separadores = regexp.MustCompile(`[.\-/\s]`)
	formato     = regexp.MustCompile(`^[A-Za-z0-9]{12}[0-9]{2}$`)
)

func calcularDV(base string) byte {
	soma, peso := 0, 2
	for i := len(base) - 1; i >= 0; i-- {
		soma += int(base[i]-48) * peso
		if peso == 9 {
			peso = 2
		} else {
			peso++
		}
	}
	resto := soma % 11
	if resto < 2 {
		return '0'
	}
	return strconv.Itoa(11 - resto)[0]
}

func Valido(entrada string) bool {
	cnpj := separadores.ReplaceAllString(entrada, "")
	if !formato.MatchString(cnpj) {
		return false
	}
	cnpj = strings.ToUpper(cnpj)
	if strings.Count(cnpj, cnpj[:1]) == len(cnpj) {
		return false
	}
	return calcularDV(cnpj[:12]) == cnpj[12] && calcularDV(cnpj[:13]) == cnpj[13]
}

Database and screens

  • Text column: store the CNPJ as CHAR(14) or VARCHAR(14), without the mask. Numeric columns (BIGINT, NUMERIC) cannot hold letters and already dropped leading zeros. To migrate a numeric column:
-- PostgreSQL: from BIGINT to text, restoring the leading zeros
ALTER TABLE empresa ALTER COLUMN cnpj TYPE CHAR(14) USING LPAD(cnpj::text, 14, '0');

-- MySQL
ALTER TABLE empresa MODIFY cnpj CHAR(14);
UPDATE empresa SET cnpj = LPAD(cnpj, 14, '0');
  • With the mask: if your system stores the formatted CNPJ, the column needs 18 characters.
  • No conversion to number: look for code that does int(cnpj), parseInt, Convert.ToInt64 or compares CNPJs as numbers. All of them break with a letter.
  • Uppercase: normalize to uppercase before storing and comparing, so that 12abc34501de35 and 12ABC34501DE35 are the same record. Even if your database compares case-insensitively, comparisons inside your code and your integrations do not.
  • Input fields: replace numeric-only masks and keyboards (such as inputmode="numeric") with fields that accept letters.

Look up an alphanumeric CNPJ via API

The lookup is the same as for any CNPJ: GET /{cnpj}, with the 14 characters and no mask. The API accepts uppercase and lowercase and validates the check digits of an alphanumeric CNPJ: with a wrong digit, the response is 422 (CNPJ inválido, invalid CNPJ). For numeric-only CNPJs, nothing changes. The same applies to the batch lookup, where a CNPJ with a wrong digit comes back as an invalido item.

To try it without an account, use the public test key with the fictitious sample 12ABC34501DE35 (see Authentication). The test key only responds to the sample CNPJs:

curl "https://api.cnpjapi.com.br/12ABC34501DE35" \
  -H "Authorization: Bearer cnpj_teste_exemplos"
{
  "CNPJ": "12.ABC.345/01DE-35",
  "RazaoSocial": "EMPRESA EXEMPLO COM REGISTROS LTDA",
  "MatrizFilial": { "Codigo": "1", "Descricao": "Matriz" }
}

With your own key, the lookup works for the real alphanumeric CNPJs in the Receita database, such as 00000000E08G12. The CNPJ field in the response always comes with the mask.

State Registration (IE)

The SEFAZ registry lookup does not accept alphanumeric CNPJs yet. So, for these CNPJs:

  • the IE lookup (GET /consulta/ie/{cnpj}) returns 422;
  • in the CNPJ lookup with ?incluir=ie, the response returns the CNPJ data with InscricoesEstaduais: null and InscricoesEstaduaisErro: "cnpj_alfanumerico", and you are not charged for the IE. This holds even without IE in your plan or with your IE quota used up: it counts as a regular CNPJ lookup.

Frequently asked questions

What is the alphanumeric CNPJ? It is a CNPJ with letters and numbers in the first 12 positions, which Receita Federal started issuing on July 31, 2026. It still has 14 characters and both check digits are always numbers.

Will existing CNPJs change? No. Existing CNPJs remain valid, with no replacement. The new format only applies to new registrations, issued gradually during 2026.

How do I calculate the check digits of an alphanumeric CNPJ? With the same modulo 11 as always, giving each character the value of its ASCII code minus 48: numbers are worth themselves and letters go from 17 (A) to 42 (Z).

Does the CNPJAPI API accept alphanumeric CNPJs? Yes. The GET /{cnpj} lookup accepts the new format, in uppercase or lowercase, and validates the check digits.

Can I look up the State Registration (IE) of an alphanumeric CNPJ? Not yet, because the SEFAZ registry lookup does not accept the new format. In the CNPJ lookup with IE, the response returns cnpj_alfanumerico in the IE error field, without charging for the IE.

Next steps