Resumo
AddressesResource aponta para address.api.nfe.io/v2, que recusa a chave principal com 403. O host anterior, open.nfe.io/v1, aceita a mesma chave com 200. Quem migra de uma integração que usava a v1 perde a consulta de CEP sem mudar nada do próprio código, e a causa não é óbvia.
Verificado na v3.5.0, em 2026-09-02, com uma conta real.
Medição
Mesma conta, mesmo CEP (01310100), duas chaves distintas:
| Host |
Sem chave |
Chave principal (apiKey) |
Chave de dados (dataApiKey) |
open.nfe.io/v1 |
401 |
200 |
200 |
address.api.nfe.io/v2 |
401 |
403 |
200 |
Ou seja, a v1 aceita qualquer uma das duas; a v2 só a de dados.
Por que isso surpreende
O Config documenta o 403 e explica a causa provável:
If you see HTTP 403 on addresses/... despite a valid main key, the most likely cause is that the main key's plan does not include the data-services product.
Isso está correto e foi útil. O que falta é a informação de que a exigência mudou junto com o host: uma integração que funcionava com uma chave só, contra a v1, passa a precisar de duas ao adotar o SDK. Lendo apenas o Config, a conclusão natural é que o plano da chave sempre foi insuficiente — quando na verdade ele era suficiente no host anterior.
Sugestões, em ordem de preferência
- Documentar a diferença no docblock de
AddressesResource e no Config: a v2 exige o produto de data-services; a v1 aceitava a chave principal. Isso sozinho já resolve o tempo perdido diagnosticando.
- Melhorar a mensagem do 403 nessas famílias, sugerindo
dataApiKey explicitamente — hoje a exceção diz apenas API request failed with HTTP 403.
- Opcionalmente, permitir configurar o host da família
addresses sem recorrer a RequestOptions(baseUrl: ...) por chamada.
Contorno que adotamos
Passar baseUrl por chamada, mantendo uma chave só:
$lookup = $client->addresses->lookupByPostalCode(
$cep,
new \Nfe\Http\RequestOptions( baseUrl: 'https://open.nfe.io/v1' )
);
Com uma ressalva: a v1 devolve o endereço como objeto plano, enquanto extractAddresses() só desembrulha o envelope {"address": {...}} da v2 (ou uma coleção {"addresses": [...]}). O resultado é $lookup->addresses === [] e os dados acessíveis apenas em $lookup->raw.
Se a v1 continuar suportada, talvez valha extractAddresses() tolerar também o corpo plano — bastaria reconhecer um payload que já tem as chaves de endereço na raiz.
Contexto
Encontrado ao migrar o plugin nfe/woo-nfe para o nfe/nfe 3.5. O plugin resolvia o código IBGE do município pelo CEP contra open.nfe.io/v1 com a chave de emissão; ao passar pelo resource do SDK, a consulta passou a devolver 403 e o campo city.code ficava nulo, o que bloqueia a emissão quando o endereço é obrigatório.
Resumo
AddressesResourceaponta paraaddress.api.nfe.io/v2, que recusa a chave principal com 403. O host anterior,open.nfe.io/v1, aceita a mesma chave com 200. Quem migra de uma integração que usava a v1 perde a consulta de CEP sem mudar nada do próprio código, e a causa não é óbvia.Verificado na v3.5.0, em 2026-09-02, com uma conta real.
Medição
Mesma conta, mesmo CEP (
01310100), duas chaves distintas:apiKey)dataApiKey)open.nfe.io/v1address.api.nfe.io/v2Ou seja, a v1 aceita qualquer uma das duas; a v2 só a de dados.
Por que isso surpreende
O
Configdocumenta o 403 e explica a causa provável:Isso está correto e foi útil. O que falta é a informação de que a exigência mudou junto com o host: uma integração que funcionava com uma chave só, contra a v1, passa a precisar de duas ao adotar o SDK. Lendo apenas o
Config, a conclusão natural é que o plano da chave sempre foi insuficiente — quando na verdade ele era suficiente no host anterior.Sugestões, em ordem de preferência
AddressesResourcee noConfig: a v2 exige o produto de data-services; a v1 aceitava a chave principal. Isso sozinho já resolve o tempo perdido diagnosticando.dataApiKeyexplicitamente — hoje a exceção diz apenasAPI request failed with HTTP 403.addressessem recorrer aRequestOptions(baseUrl: ...)por chamada.Contorno que adotamos
Passar
baseUrlpor chamada, mantendo uma chave só:Com uma ressalva: a v1 devolve o endereço como objeto plano, enquanto
extractAddresses()só desembrulha o envelope{"address": {...}}da v2 (ou uma coleção{"addresses": [...]}). O resultado é$lookup->addresses === []e os dados acessíveis apenas em$lookup->raw.Se a v1 continuar suportada, talvez valha
extractAddresses()tolerar também o corpo plano — bastaria reconhecer um payload que já tem as chaves de endereço na raiz.Contexto
Encontrado ao migrar o plugin nfe/woo-nfe para o
nfe/nfe3.5. O plugin resolvia o código IBGE do município pelo CEP contraopen.nfe.io/v1com a chave de emissão; ao passar pelo resource do SDK, a consulta passou a devolver 403 e o campocity.codeficava nulo, o que bloqueia a emissão quando o endereço é obrigatório.