DEV Community

build996
build996

Posted on

Groq returned the same 403 for my real key, a fake key, and no key

I was running a small Node benchmark script against a few hosted LLM APIs from a Windows machine. One provider failed with a bare fetch failed, which at least looked like a network problem. Groq did something more convincing. It answered, with a status code and a JSON body:

HTTP 403
{"error":{"message":"Forbidden"}}
Enter fullscreen mode Exit fullscreen mode

A 403 from an API that has your key reads like "your key is not allowed to do this". Revoked, suspended, wrong project, account flagged. What stopped me from treating it that way was one boring test.

Three requests, one answer

If the 403 is about the key, then changing the key should change the answer. So I sent the same request three ways:

  1. with my real key
  2. with a key I made up (gsk_ plus random characters)
  3. with no Authorization header at all

All three came back identical: 403, {"error":{"message":"Forbidden"}}.

That settles more than it looks like. A server that has actually looked at your credentials cannot give the same verdict to a valid key, a fabricated one and a missing one. Whatever said "Forbidden" made its decision before anyone checked the key. The key was never the problem, so rotating it, regenerating it or emailing support about it would all have been wasted.

What a real auth failure looks like

For contrast, here is the same endpoint today, from a network path that works, with a fake key and then with no key at all:

curl -s -w "\n%{http_code}\n" https://api.groq.com/openai/v1/models \
  -H "Authorization: Bearer gsk_fake123"

curl -s -w "\n%{http_code}\n" https://api.groq.com/openai/v1/models
Enter fullscreen mode Exit fullscreen mode

Both return:

{"error":{"message":"Invalid API Key","type":"invalid_request_error","code":"invalid_api_key"}}
401
Enter fullscreen mode Exit fullscreen mode

Two differences from the 403, and either one is enough to tell them apart:

  • The status. A missing or wrong key gets 401. A 403 for a request that carried no credentials at all doesn't mean "this key lacks permission", because there was no key to judge.
  • The body shape. The real auth error has type and code fields, the same shape as the API's other errors. The 403 body had only message. When an error doesn't look like the rest of the API's errors, it probably didn't come from the API's application code.

I can't reproduce the 403 any more because the network path it came from is gone. Where exactly it was produced, I can't say for certain: something between my machine and the key check, whether a network hop or an edge policy that looks at where a request comes from. What I can say is that it was upstream of authentication, and the three-key test is what proved it.

Why curl worked and Node didn't

The second half of this was a red herring of my own making. The machine had a proxy configured through HTTPS_PROXY, and curl through that proxy reached everything fine. So I assumed my script was taking the same path. It wasn't.

Node's built-in fetch (undici) does not read HTTP_PROXY / HTTPS_PROXY by default. The script inherited the variables and ignored them, so its requests went out directly, which is where the 403 and the fetch failed both came from. One provider answering normally over that same direct path made it look provider-specific instead of path-specific, which sent me further in the wrong direction.

On Node 24 there's an opt-in for this:

NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://127.0.0.1:PORT node script.mjs
Enter fullscreen mode Exit fullscreen mode

With that set, the same script got normal answers from every provider, including Groq with the same key that had been "forbidden" before.

The checklist I use now

When an API rejects a key that I believe is good:

  1. Send the request with no Authorization header. If you get the same status and body as with your key, the key isn't being evaluated. Stop debugging the key.
  2. Compare the error body's shape with the API's documented errors. Missing fields usually means a different layer wrote it.
  3. Make sure the tool that fails takes the same path as the tool that works. curl honouring HTTPS_PROXY says nothing about Node's fetch, Python's httpx with trust_env=False, or anything else with its own idea of proxies.

Step 1 takes ten seconds, and it's the one I skipped. I'm curious whether other people have a "this error isn't from where it claims to be" test they reach for first, because the general problem (an error message that blames the wrong layer) seems to come up everywhere, not just with API keys.

Top comments (1)

Collapse
 
m_montazeri profile image
Mohammad Montazeri •

Small qualification to the diagnostic rule: identical 403 responses with real, fake and missing keys suggest a shared rejection path, but alone do not prove authentication was never evaluated. RFC 9110 §15.5.4 allows both credential-related and unrelated refusals. Your same-key success after changing the proxy path is stronger evidence for this incident. A provider request ID correlated with authorized server logs could narrow the rejecting layer further, without posting keys or full headers. I haven't reproduced the setup.

AI-assisted reply; I work on a VPN service.