When a chat completion request fails silently or returns a cryptic 400, reaching for curl -v debug llm api errors is the fastest way to see what actually went over the wire. The verbose flag exposes the full HTTP transaction—TLS handshake, headers, and body—without needing to instrument your SDK or wait for a logging pipeline.
Most LLM providers ship OpenAI-compatible REST endpoints, so the debugging playbook is identical whether you target OpenAI, a self-hosted vLLM instance, or a gateway. Below is an end-to-end procedure to isolate the fault.
Step 1: Strip the SDK and reproduce with a bare curl call
SDKs wrap errors in retry logic and obscure the raw request. Before anything else, reproduce the failure with a single curl command. Set your key in the environment to avoid leaking it into shell history.
export OPENAI_API_KEY="sk-..."
curl -v https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}'
If this minimal call succeeds, your SDK code is the problem—not the API. If it fails, the verbose output is your ground truth.
Step 2: Decode the verbose output layers
curl -v prints three distinct phases. Learn to scan them in order.
Connection and TLS setup
Lines prefixed with * are curl internals. You’ll see DNS resolution, TCP connect, and the TLS handshake:
* Trying 104.18.12.34:443...
* Connected to api.openai.com (104.18.12.34) port 443
* TLS handshake performed, cipher TLS_AES_256_GCM_SHA384
A failure here is infrastructure, not your payload. * SSL certificate problem means a proxy is doing TLS interception; * Connection refused means a firewall or wrong port.
Outgoing request line and headers
Lines starting with > are what curl sent. Confirm the method, path, and Host:
> POST /v1/chat/completions HTTP/2
> Host: api.openai.com
> Authorization: Bearer sk-...
> Content-Type: application/json
Missing Authorization or a malformed Content-Type will trigger 401 or 415 before the provider even parses your body.
Request body
With -d, curl prints the body after the headers, often as > { [length] bytes data] } followed by the raw JSON if you use --trace-ascii -. For clarity, add --trace-ascii /tmp/trace.txt and inspect the file. A truncated body causes 400 errors that look like schema violations.
Step 3: Identify common LLM API error signatures
The response phase starts with < lines. The status code is the first thing to check.
400 Bad Request — Usually malformed JSON or a missing required field like messages. The body will contain an error object with param and type. With curl -v debug llm api errors, you can confirm whether your model string is misspelled by comparing against the provider’s model list.
401 Unauthorized — The Authorization header is missing or the key is revoked. Check the > block; if the header is absent, your shell variable didn’t expand.
429 Too Many Requests — Rate limit. The response headers show retry-after. If you’re behind a gateway that aggregates providers, you may see a 429 even when the underlying provider is healthy because the gateway’s own quota is exhausted.
502 / 503 — Provider degradation. The body may be empty. This is where a gateway with automatic fallback shines: a correctly configured layer will retry a second provider and return 200 with a different x-routed-provider header. Without curl -v you’d never see the retry happened.
Example verbose snippet for a 429:
< HTTP/2 429
< content-type: application/json
< retry-after: 2
< x-ratelimit-remaining: 0
Step 4: Compare against a known-good request
Save a working call’s trace to a file. When something breaks after a library upgrade, diff the two.
curl -v https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}' \
--trace-ascii /tmp/good.txt
# after failure
curl -v https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"pong"}]}' \
--trace-ascii /tmp/bad.txt
diff /tmp/good.txt /tmp/bad.txt
Focus on differences in the > headers and the JSON body. A single extra comma or wrong content-type breaks the parse.
Step 5: Inspect gateway and cache-control headers
If you route through an OpenAI-compatible gateway, the response headers carry routing and caching metadata. For instance, n4n.ai exposes a single endpoint that fronts 240+ models and honors client routing directives; when a provider is rate-limited it fails over automatically and forwards the upstream cache-control hints. Running curl -v surfaces those headers so you can confirm a fallback occurred:
< x-routed-provider: anthropic
< cache-control: max-age=3600
< x-fallback: true
If you expected a specific provider via x-route-to and the header shows another, your routing directive wasn’t honored. That’s a config bug, not a model bug.
Step 6: Script a repeatable debug harness
Don’t retype the command on every incident. Wrap it in a shell function that prints only the lines you care about.
debug_llm() {
local url="$1"; local body="$2"
curl -v "$url" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "$body" \
--trace-ascii - 2>&1 \
| grep -E '^(> |< |\* |\{ \[)'
}
debug_llm https://api.openai.com/v1/chat/completions '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}'
For programmatic checks, a Python snippet using subprocess captures the status code:
import subprocess, os, json
def probe(url, payload):
env = os.environ.copy()
cmd = ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "-v", url,
"-H", f"Authorization: Bearer {env['OPENAI_API_KEY']}",
"-H", "Content-Type: application/json",
"-d", json.dumps(payload)]
r = subprocess.run(cmd, capture_output=True, text=True)
print(r.stderr) # verbose goes to stderr
return r.stdout
print(probe("https://api.openai.com/v1/chat/completions",
{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}))
Step 7: Verify the fix
Success means the curl -v debug llm api errors run shows HTTP/2 200 and the < body parses as JSON with an id and choices array. Confirm there are no retry-after or x-fallback headers unless you explicitly triggered a routed fallback.
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}' \
| python -c "import sys,json; d=json.load(sys.stdin); print(d['choices'][0]['message']['content'])"
If that prints a non-empty string, the integration is healthy. The verbose trace is no longer needed; remove -v from production calls to cut log noise.
Practical notes from the trenches
- Always set
-H "Content-Type: application/json". Some gateways default to form encoding and return 400 with an unhelpful message. - Use
--compressedif the endpoint sends gzip;curl -vwill show thecontent-encodingheader and confirm decompression. - For streaming endpoints, add
-Nand watch the<chunks arrive; a stalled stream shows as a gap in the trace. - If you see
HTTP/1.1 100 Continue, that’s normal for large bodies; don’t mistake it for an error.
Debugging LLM API errors is mostly about removing abstraction layers. curl -v is the lowest level short of tcpdump, and it’s enough to pinpoint 90% of integration failures. Keep the trace files; they’re the fastest way to prove to a provider’s support that the request they received wasn’t what your code thought it sent.