HTTP STATUS 400 ao tentar baixar XML de CTe 4.0

Erro estranho ao tentar baixar o XML de CTe autorizado.
uid: cte_3a1120d42c874b2d96181927df2ecd6a
referencia: 3e95c666-da25-11ee-928d-9457a55bde60
endpoint: https://api.nuvemfiscal.com.br/cte/cte_3a1120d42c874b2d96181927df2ecd6a/xml
Retorno: 400
“response”: “<?xml version=\"1.0\" encoding=\"UTF-8\"?>InvalidRequestMissing x-amz-content-sha256”
Observação: O PDF da DACTE foi obtido sem problemas, somente com o XML é que ocorre este erro.

Bom dia, @nilton.

Aparentemente, você está recebendo o erro na requisição da URL de redirecionamento.

Para mais informações, favor ler o seguinte tópico:

Poxa Arimatea, não recebi nenhum email sobre esta alteração que também afeta as DACTE’s, utilizo Harbour como linguagem de programação para isto, não entendi o que devo alterar no endpoint, vou analisar a documentação do endpoit para baixar XML no CTe e ver o que mudou.
Eu olhei a documentação, utilizo o endpoint “Baixar XML do CT-e processado”
https://api.nuvemfiscal.com.br/cte/{id}/xml
Não houve alteração alguma na documentação
O que extamente devo alterar em meu programa para obter o XML? No tópico que você indicou, não deixa muito claro o que devo alterar na requisição e nem tem nada na documentação. Devo usar outro endpoint? Por favor, fico no aguardo de uma orientação.
Grato!

Complementando: Eu testei utilizando o Insomnia da mesma forma que minha aplicação o faz e o XML retornou sem problemas pelo Insomnia com código 200 ao invés de 302 ou 307 conforme tópico e não veio compactado (gzip) . O que não estou entendendo?
Grato.

Olá @nilton, a comunicação foi enviada via e-mail no dia 24/01/2024 com o assunto “Novidades: Alteração importante nos endpoints de baixa de XML e DANFE”.

Também foi publicado no link que o @arimateia postou acima. Você pode acompanhar todos os postos na categoria releases, vá na URL Tópicos etiquetados com releases e clique no ícone do sino no canto superior direito. Clique em “Acompanhando” e receberá notificação sempre que um novo tópico for criado nessa categoria.

O Insomnia já faz esse processamento automático pra você. Se quiser testar o Insomnia sem esse recurso, você tem que desabilitar o redirecionamento automático, nas configurações dele:

Da mesma forma que ele está resolvendo o gzip automaticamente.

Obrigado pelo retorno Wagner, no caso da minha aplicação em Harbour, uso a DLL da Microsoft win_oleCreateObject(“MSXML2.ServerXMLHTTP.6.0”), o que eu mudo na requisição? Não achei na documentação da MSXML2 algo como redirects. Pode me ajudar?

Eu desabilitei a opção “Follow redirects” no Insomnia para testar o response, mesmo assim foi retornado codigo 302 e o location, só não trouxe o XML, já na minha aplicação, não retorna o código 302, retorna um erro 400 com o response: “response”: “<?xml version=\"1.0\" encoding=\"UTF-8\"?>InvalidRequestMissing x-amz-content-sha256”, não me dando a opção de obter o location para redirecionar a requisição.

@nilton

Como citei anteriormente, o erro parece ser exatamente na requisição da URL de redirecionamento. Ou seja, quem está retornando o status code 400 não é a API da Nuvem Fiscal e sim o serviço da Amazon S3 ou Cloudflare R2.

Suspeito que o header “authorization” está sendo incluído indevidamente na requisição pela sua biblioteca. Poderia verificar isso através do Fiddler, Wireshark ou algum outro software similar?

Ainda, você pode colocar aqui o código exato que você usa pra fazer a requisição à API da Nuvem?

Segue o código, não sei se vai ajudar, mas as únicas informações enviada no Header são:

  • Autorization Bearer
  • Accept /
#include "hmg.ch"

/*
    Broadcast: Transmitir
    Transmite à API da Nuvem Fiscal a solicitação (endpoint) e json
    (body) de acordo com o método http solicitado.
*/
function Broadcast(connection, httpMethod, apiUrl, token, operation, body, content_type, accept)
    local oError, log := {=>}
    local response := {"error" => false, "http_status" => 0, "ContentType" => "", "response" => "", "sefazOff" => {=>}}
    local sefazOFF

    try

        connection:Open(httpMethod, apiUrl, false)
        connection:SetRequestHeader("Authorization", "Bearer " + token)

        if Empty(content_type)
            content_type := ""
        else
            connection:SetRequestHeader("Content-Type", content_type)   // Request Body Schema
        endif
        if !Empty(accept)
            connection:SetRequestHeader("Accept", accept)
        endif

        if Empty(body)

            try
                connection:Send()
            catch oError
                if (oError:genCode == 0)
                    apiLog({"type" => "Error", "description" => "Erro em WinOle MSXML6.DLL"})
                    Break
                else
                    if ("O tempo limite da opera" $ oError:description)
                        apiLog({"type" => "Error", "description" => oError:description + " ... Tentando mais uma vez..."})
                        SysWait(10)  // Aguarda 10 segundos e tenta novamente
                        connection:Send()
                    else
                        apiLog({"type" => "Error", "description" => "Erro em Send() para API Nuvem Fiscal: " + oError:description})
                        Break
                    endif
                endif
            end

        else
            // Request Body
            try
                connection:Send(body)
            catch oError
                if (oError:genCode == 0)
                    apiLog({"type" => "Error", "description" => "Erro em WinOle MSXML6.DLL"})
                    Break
                else
                    if ("o tempo limite da opera" $ Lower(oError:description))
                        apiLog({"type" => "Error", "description" => oError:description + " ... Tentando mais uma vez..."})
                        SysWait(10)  // Aguarda 10 segundos e tenta novamente
                        connection:Send(body)
                    else
                        apiLog({"type" => "Error", "description" => "Erro em Send() para API Nuvem Fiscal: " + oError:description})
                        Break
                    endif
                endif
            end

        endif

        if ("image" $ content_type)
            connection:WaitForResponse(70000)
        else
            connection:WaitForResponse(5000)
        endif

    catch oError

        log["type"] := "Error"
        log["method"] := httpMethod
        log["url"] := apiUrl
        log["content_type"] := iif(content_type == nil, "$$null$$", content_type)
        log["accept"] := iif(accept == nil, "$$null$$", accept)
        log["body"] := iif(body == nil, "$$null$$", iif("image" $ content_type, "[ ARQUIVO BINARIO DA IMAGEM ]", body))

        if (oError:genCode == 0)
            log["description"] := "Erro desconhecido de conexão com o site"
            log["response"] := "Erro desconhecido de conexão com o site " + operation
            response["response"] := "Erro de conexão com a API Nuvem Fiscal em " + operation
        else
            log["description"] := oError:description
            log["response"] := "Erro de conexão com API Nuvem Fiscal em " + operation
            response["response"] := "Erro de conexão com a API Nuvem Fiscal em " + operation + " | " + oError:description
        endif
        apiLog(log)
        log := nil
        response["error"] := true
        response["ContentType"] := "text"
        Break
    end

    if !response["error"]

        response["http_status"] := connection:Status

        if (response["http_status"] > 199) .and. (response["http_status"] < 300)

            // Entre 200 e 299
            if !Empty(connection:ResponseBody)
                response["response"] := connection:ResponseBody
                response["ContentType"] := "json"
            endif

        else    // elseif (response["http_status"] > 399) .and. (response["http_status"] < 600)

            if ("json" $ connection:getResponseHeader("Content-Type"))

                // "application/json"
                response["ContentType"] := "json"
                response["response"] := connection:ResponseBody

                sefazOFF := hb_jsonDecode(response["response"])

                if hb_HGetRef(sefazOFF, "status") .and. hb_HGetRef(sefazOFF, "autorizacao")

                    sefazOFF := sefazOFF["autorizacao"]

                    if hb_HGetRef(sefazOFF, "motivo_status")
                        if "the server name cannot be resolved" $ Lower(sefazOFF["motivo_status"])
                            response["sefazOff"]["id"] := sefazOFF["id"]
                            response["sefazOff"]["codigo_status"] := sefazOFF["codigo_status"]
                            response["sefazOff"]["motivo_status"] := sefazOFF["motivo_status"]
                        elseif response["http_status"] == 500 .and. ("internal server error" $ Lower(sefazOFF["motivo_status"]))
                            log["type"] := "Error"
                            log["method"] := httpMethod
                            log["url"] := apiUrl
                            log["content_type"] := iif(content_type == nil, "$$null$$", content_type)
                            log["accept"] := iif(accept == nil, "$$null$$", accept)
                            log["body"] := iif(body == nil, "$$null$$", iif("image" $ content_type, "[ ARQUIVO BINARIO DA IMAGEM ]", body))
                            log["description"] := "HTTP Status: 500 - Internal Server Error, " + sefazOFF["motivo_status"]
                            log["response"] := iif(response["response"] == nil .or. Empty(response["response"]), "$$null$$", ;
                                iif((Lower(Left(operation, 6)) == "baixar"), "Response é um ARQUIVO BINÁRIO", response["response"]))
                            apiLog(log)
                            MsgStop({"Erro no servidor da api de DFe", hb_eol(), "Erro: ", sefazOFF["motivo_status"]}, "DFeMonitor " + appData:version + ": Erro HTTP:500")
                            turnOFF()
                        endif
                    endif

                endif

            else
                // "application/text"
                response["ContentType"] := "text"
                if !Empty(connection:ResponseText)
                    response["response"] := connection:ResponseText
                elseif !Empty(connection:ResponseBody)
                    response["response"] := connection:ResponseBody
                else
                    response["response"] := "ResponseText e ResponseBody retornaram vazio, sem mensagem"
                endif
            endif

            response["error"] := true

        endif

        log["type"] := "Information"
        log["method"] := httpMethod
        log["url"] := apiUrl
        log["content_type"] := iif(content_type == nil, "$$null$$", content_type)
        log["accept"] := iif(accept == nil, "$$null$$", accept)
        log["body"] := iif(body == nil, "$$null$$", iif("image" $ content_type, "[ ARQUIVO BINARIO DA IMAGEM ]", body))
        log["description"] := "HTTP Status: " + hb_ntos(response["http_status"]) + " - " + operation
        log["response"] := iif(response["response"] == nil .or. Empty(response["response"]), "$$null$$", ;
            iif((Lower(Left(operation, 6)) == "baixar"), "Response é um ARQUIVO BINÁRIO", response["response"]))

        apiLog(log)

    endif

return response

Parece ser uma limitação desse objeto. Algumas referências:

Seria interessante verificar em um Fiddler, como disse o Arimateia, pra ver se o IXMLHTTP está incluindo o Header Authorization no redirect. Isso não é desejado, e parece ser o que está ocorrendo.

E, infelizmente, parece que você como programador não tem muito controle sobre isso. Não há um suporte no Harbour que eles possam te orientar como fazer, ou usar uma outra biblioteca para requisição HTTP?

O Harbour é fraco em blibliotecas para API’s, mas estive pesquisando, tem outra dll da MS (WinHttp.WinHttpRequest.5.1) que estou testando, ela permite setar o redirecionamento, estou testando mas o método Option(6) não está aceitando alterar pra true, a documentação da MS também é muito ruim.
Bom, o importante é que este problema só aconteceu com um CTe, meu cliente emititu vários hoje a tarde e nenhum deles retornou aquele erro 400. Parece ser um caso de isolado de momento. Vai saber?!..
Ah, só pra informar: Estou migrando aos poucos minha aplicação com Node.JS, Tyscript, Next.JS com React. Com essas ferramentas não terei problemas com bibliotecas para me conectar com APIs.

Este tópico foi fechado automaticamente 24 horas depois da última resposta. Novas respostas não são mais permitidas.