Gemini API/Lucee give 404 but works in Postman

This is technically related to Gemini but wondering if anybody has any insight.

I signed up for a free version of Gemini so I could test out their API. I have created a key and it works perfectly in Postman when i do a raw JSON post with the url https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=MyKey

With the body

{
  "contents": [
    {
      "parts": [
        {
          "text": "how big is the sun. Tell me in less than 20 characters"
        }
      ]
    }
  ]
}

However, I’ve tried a million and one ways or so it feels to try to get the same thing to work in Lucee, and it keeps kicking back a 404 error. Now the 404 is a bit misleading because I also posted the endpoint into a browser and got the 404 response because the body was missing, but it demonstrated that it gave a 404 even though the endpoint URL was correct.

Here’s one of the many attempts

<cfscript>
// API endpoint - DOUBLE CHECK THIS
apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent";
// API key (IMPORTANT: Define this variable securely!)
apiKey = GEMINI_API_KEY;
// *** URL-encode the API key ***
apiKey = urlEncodedFormat(apiUrl);
cfhttp(url="#apiUrl#?key=#apiKey#", method="post", result="httpResponse") {
    cfhttpparam type="header" name="Content-Type" value="application/json; charset=UTF-8"
    cfhttpparam type="body" value='{"contents": [{"parts":[{"text": "test"}]}]}'
}
writeDump(httpResponse);
</cfscript>

here’s another

<cfscript>
// API endpoint from the working curl example
apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent";

// API key (IMPORTANT: Define this variable securely!)
apiKey = GEMINI_API_KEY;

// Payload from the working curl example
payload = {
    "contents": [
        {
            "parts": [
                {
                    "text": "Tell me about the earth size in less than 30 characters"  // Or "Write a story about a magic backpack."
                }
            ]
        }
    ]
};

// Serialize payload to JSON
payloadJson = serializeJSON(payload);

// *** Debugging: Ensure payloadJson is valid JSON ***
try {
    deserializeJSON(payloadJson); // Try to deserialize it back
    writeOutput("<br>Payload JSON is valid.<br>");
} catch (any e) {
    writeOutput("<br>Payload JSON is NOT valid: #e.message#<br>");
    // If it's invalid, inspect payloadJson closely!
}

// cfhttp to make the API call
cfhttp(url=apiUrl, method="post", result="httpResponse") {
    // API key in the URL (as it works in your Postman example)
    cfhttpparam(type="url", name="key", value="#apiKey#");

    // Content-Type header (with charset) - EXACT MATCH TO POSTMAN
    cfhttpparam(type="header", name="Content-Type", value="application/json; charset=UTF-8");

    // Set the JSON payload as the body
    cfhttpparam(type="body", value=payloadJson);
}

// *** Debugging: Output httpResponse ***
writeDump(httpResponse); // Inspect the entire httpResponse struct

// Process the API response
if (httpResponse.statusCode eq "200 OK") {
    // Successful response
    responseData = deserializeJSON(httpResponse.fileContent);

    // *** Extract the answer - ADJUST THIS BASED ON ACTUAL RESPONSE STRUCTURE ***
    // *** The structure of the response will vary depending on the Gemini model ***
    if (structKeyExists(responseData, "candidates") && arrayLen(responseData.candidates) gt 0) {
        // This is a likely path to the answer, but you *must* verify it
        answer = responseData.candidates[1].content.parts[1].text;
        // *** Gemini API does not provide token usage in the same way as OpenAI ***
        tokensUsed = "N/A";
    } else {
        answer = "Error: No answer received from Gemini API";
        tokensUsed = "N/A";
    }

    // Output the results
    writeOutput("Question: Tell me about the earth size in less than 30 characters<br>");
    writeOutput("Answer: #answer#<br>");
    writeOutput("Tokens Used: #tokensUsed#");

} else {
    // *** Handle errors from the Gemini API ***
    writeOutput("Error: #httpResponse.status_code# - #httpResponse.status_text#<br>");
    writeOutput("Response Content: #httpResponse.fileContent#");

    // *** Log the error for debugging ***
    // <cflog file="GeminiAPIError" text="Status: #httpResponse.statusCode#, #httpResponse.statusText#, Content: #httpResponse.fileContent#">

    // *** Handle specific errors, such as rate limiting ***
    if (httpResponse.status_code eq "429") {
        writeOutput("<br>Rate limit exceeded. Please try again later.");
        // Implement retry logic if needed (e.g., wait and try again)
    }
}
</cfscript>

I’d use gemini and Deepseek to try and create these solutions (hence all the comments in there)

I’m hoping somebody else has come across this issue and might be able to help. Btw I’m running it locally on HTTP but I did also try it on HTTPS

Tech stack

  • Lucee 6.1.1.118 (updating to 6.2.shortly)
  • Java 11.0.21 (Eclipse Adoptium) 64bit
  • Windows Server 2022 (10.0) 64bit
  • IIS 10

Since you can’t trust the 404 (and it failed when there was simply no body), you can’t trust that status code to mean anything more than a general failure from the server, it seems.

First, I see you outputting parts of the cfhttp variable on failure. Dump it all. There maybe more there than you’d expect. (I’m reading this on a phone. If I missed you’d done that, sorry.)

Second, it could be that the server (or some proxy they use) is rejecting the request for not having a recognized user agent header. In cf it would be “coldfusion” IIRC, not sure about Lucee. But cfhttp has a useragent attribute or you can set it as a cfhttpparam of type=“header”. See what postman was setting and emulate that.

Also, you can help yourself by having both postman and your cfhttp call a test cf page, where you dump cf’s gethttprequestheaders() function (or gethttprequestdata() in ACF or Lucee), to see just WHAT each is passing in. That can help verify what you do in cfhttp, or help better have cfhttp mimic what postman sends. That will help then in calling the service instead.

HTH. Someone may have better specifics for your situation.

1 Like

I THINK that postman actually tweaks it’s headers accordingly to the domain, so if it knows it’s google gemini it may be adding bits and bats. If I hit my own server I’m not sure it will help, but I can try.

I did try the postman user agent, it didn’t help.

I just tried your suggestion. I set up an API on my server to receive and lot results, then hit it from my local machine using several variations of code (I’ve been using AI to try and come up with solutions) and also from Postman. There was a noticeable difference in the way the body was formatted, the version from Lucee using CFHTTP was compacted, however I then went an compacted it on postman and checked the log and it does not add any spaces, I tested a compacted version against gemini and it also worked. so that can’t be the issue either.

result from a post from my dev to server

=== New Request ===\nHeaders:\nuser-agent: Lucee (CFML Engine)\ncert-keysize: 256\nappl-physical-path: C:\ACS\showtheoffer\\nx-tomcat-docroot: C:\ACS\showtheoffer\\nserver-port-secure: 1\nxajp-path-info: \nremote-port: 61225\nhost: www.showtheoffer.com\ncontent-type: application/json\naccept-encoding: gzip\nconnection: Keep-Alive\nhttps-server-issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=RapidSSL TLS RSA CA G1\ncert-server-issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=RapidSSL TLS RSA CA G1\nscript-name: /logrequest.cfm\ncontent-length: 88\ngateway-interface: CGI/1.1\npath-info: /logrequest.cfm\nx-webserver-context: W3SVC19\nhttps-server-subject: CN=showtheoffer.com\ncert-secretkeysize: 2048\nhttps: on\nserver-port: 443\nlocal-addr: 104.192.7.253\nhttps-keysize: 256\ncert-server-subject: CN=showtheoffer.com\nx-modcfml-sharedkey: d47bc6bc194e30e063ba21ef871707308d469402c90d4e367d0e1b7fbd740700\nBody:\n{"contents":[{"parts":[{"text":"In less than 10 words tell me how big the earth is"}]}]}\n\n

This is the log result to the same URL using postman

=== New Request ===\nHeaders:\nuser-agent: PostmanRuntime/7.43.2\ncert-keysize: 256\nappl-physical-path: C:\ACS\showtheoffer\\nx-tomcat-docroot: C:\ACS\showtheoffer\\nserver-port-secure: 1\nxajp-path-info: \nremote-port: 61496\nhost: www.showtheoffer.com\naccept: */*\nPostman-Token: 9373b78f-9885-44d2-b61e-e6c69c7efcd5\ncontent-type: application/json\nCache-Control: no-cache\naccept-encoding: gzip, deflate, br\nconnection: keep-alive\nhttps-server-issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=RapidSSL TLS RSA CA G1\ncert-server-issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=RapidSSL TLS RSA CA G1\ngemini-api-key: key=AIzaSyA00PHueErXGyr1Jky6RwGhyfjQwcQPWGE\nscript-name: /logrequest.cfm\ncontent-length: 167\ngateway-interface: CGI/1.1\npath-info: /logrequest.cfm\nx-webserver-context: W3SVC19\nhttps-server-subject: CN=showtheoffer.com\ncert-secretkeysize: 2048\nhttps: on\nserver-port: 443\nlocal-addr: 104.192.7.253\nhttps-keysize: 256\ncert-server-subject: CN=showtheoffer.com\nx-modcfml-sharedkey: d47bc6bc194e30e063ba21ef871707308d469402c90d4e367d0e1b7fbd740700\nBody:\n{
  "contents": [
    {
      "parts": [
        {
          "text": "how big is the sun. Tell me in less than 20 characters"
        }
      ]
    }
  ]
}\n\n



You didn’t change the user agent. Try that, before you try to align other values.

And by that I mean, you don’t show changing it in the second reply. I just want to help you make sure that when you DO try to change it, you DO see it reflected in the incoming headers to your cf test page

Anyway, then yes align the other values so the cfhttp sends on what postman does.

That was just an example of many. I did actually change the user agent in several tests.

Try 6.2! Always try the latest version if you have a problem, otherwise you’re just wasting time on tech debt

1 Like

If 6.2 still fails to solve this, Mark (aspirenet), there are still options to diagnose it.

I was asking first if you confirmed THAT your change of the user agent in cfhttp WAS being reflected correctly in the dump of the test page (indeed, specifically in the cgi.http_user_agent). Yes or no?

Then have you gotten the cfhttp call to offer all the other headers and body (as showing identically to postman), as presented to that test page? Yes or no?

If the answer to both is yes, then before concluding either “Lucee has a problem” or “somehow it just can’t be done”, let’s hear what happens if you run your call to the api via a cf install. That may be a helpful comparison/sanity check.

As you may know, one can setup to run cf in a matter of minutes–for free–whether via Commandbox (if you use it) or via cf’s zip install (which is like Lucee Express) or the traditional cf installer. And it can be stopped or even uninstalled in seconds.

Or someone might want to suggest trycf.com or cffiddle.org, but I’ll understand if you may be leery of running code there which involves your api’s key (and though one could serve that in some way other than putting the key there, it may not be worth the effort here).

I hope you’re game to proceed with this, if 6.2 doesn’t do it. You’ll have the answer in less time than it took me to write this–which took far longer than merely typing the letters! :slight_smile:

we have an epic about some of these type of problems!

https://luceeserver.atlassian.net/browse/LDEV-5171

This was missing a fix version so it didn’t show up in the changelog for 6.2

https://luceeserver.atlassian.net/browse/LDEV-3349

might be worth trying
cfhttp encodeurl="false"

6.2 also has some experimental AI support built in :slight_smile:

2 Likes

It’ll surely be cool if the change to 6.2 alone solves things for Mark. Thanks for surfacing that info.

I don’t know about other folks, but I don’t feel that I can effectively search and interpret the depth of knowledge shared in that Jira ticket repo. So it’s great to have help from those who can, or at least who are aware of specific discussions/solutions there.

Either way, I hope what I shared may help him or others with another diagnostic approach to solving problems.

1 Like

my point being, I’m happy to dive and explore problems with the latest release

but I won’t be triaging any bugs with older releases

Since that’s a reply to me, I’ll say “ok, good for everyone to hear” (about no triaging of bugs for other than for 6.2 if that’s what you mean)… but to be clear it doesn’t counter anything I’ve said in this thread. I’ve not suggested there’s any bug; I’ve simply tried to help him diagnose the problem. :slight_smile:

Again, if 6 2 solves it for him, sweet. :slight_smile:

Also, thanks of course for saying you’re “happy to dive and explore problems”. Perhaps that was about the challenge I’d shared about digging into Lucee Jira tickets. I realize that’s a daily drive for some folks (using Jira for their own projects, or those working actively on the Lucee engine). It’s just not one for everybody, of course. :slight_smile: So such an offer to help is certainly appreciated.

Thanks for all the ongoing help. I actually had 6.2 on a server (I should have said, and it did not work either). I have updated my development box to 6.2 now as well. So I ma on Lucee 6.2.0.321.

cftry is a good idea, I don’t mind exposing the key for a short amount of time, I can just delete and create a new one as nothing is in production, I’m just working on a local POC

Well, the CFTRY and CFFIDDLE helped me find the issue.

CFTRY “Current engine Lucee 5”. It failed same issue, 404 error. I changed it to use CF2023, and it worked.
CFFIDDLE - Tested on CF2021 and 2025, it worked


I then added encodeurl=“false” to the CFHTTP as suggested by @Zackster and … it worked!!! I’d spent most of the day trying to fix this :(. I had a feeling it was something reasonable simple.

I tried cftry on coldfusion with the encodeurl and the code threw an error, so I guess this format is for Lucee, which is fine for me.

THANK YOU both and THANK YOU @Zackster for the solution! I can now switch to use Gemini :slight_smile:

2 Likes

I feel your pain, I had the same problem trying download a blob from github actions!

https://luceeserver.atlassian.net/browse/LDEV-5172

Just added a bold tip to the docs page for cfhttp

Cool, glad some value for the community came out of this. It’s normally me just asking lots of questions. AI is keeping me reasonably quiet on the forum. Just off the subject of the thread. Grok seems to be better at Lucee than ChatGPT, Gemini and Deepseek. Deepseek might be a second.

1 Like

I’ve been using https://perplexity.ai myself

i.e. https://www.perplexity.ai/search/api-call-with-postman-works-bu-jZtAyjaOSGKI7XaI6K4yfw

Keep reading my words being regurgitated.

But it speaks volumes about how important it is to ask questions and respond here on the forum, so much information is lost on slack

Also with 6.2 you can hook up an AI to analyze your exceptions directly in error template

+1 for perplexity, which I’ve enjoyed for some time along with its nice mobile app.

And glad all got resolved for you, Mark. Happy to have helped, and indeed kudos to Zac for the encodeurl attribute solution.