The curl openai chat completions api call is the fastest way to validate credentials, inspect raw HTTP behavior, and script inference without pulling in a language SDK. With a single command you can send a chat prompt and get structured JSON back, which makes it ideal for debugging proxy layers or CI checks. This guide walks through each step from key setup to streaming and error handling, with runnable snippets you can paste into a terminal.
Step 1: Export your API key
Never hardcode secrets in shell history or commit them to a repo. Set the key as an environment variable for the session:
export OPENAI_API_KEY="sk-..."
If you run this in a shared shell, unset it when done (unset OPENAI_API_KEY). For scripts, source the value from a local .env file that is git-ignored. The OpenAI REST endpoint reads the key from the Authorization header as a bearer token; curl supplies it on every call. If the variable is empty, you will get a 401 with no detailed error, so verify it is set first:
test -n "$OPENAI_API_KEY" && echo "key present" || echo "key missing"
Step 2: Send a minimal chat completion request
The simplest curl openai chat completions api invocation posts a JSON body with a model name and a messages array. Use gpt-4o-mini (a real, generally available model) to keep cost low:
curl 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": "Say hello in one word."}]
}'
The endpoint returns HTTP 200 with a JSON object containing choices. A 401 means the key is wrong or unset. A 404 means the model name is invalid for your account tier. A 400 means malformed JSON—curl will not catch that for you, so validate with jq before sending if you build the body dynamically.
Step 3: Parse and verify the response
Pipe the output to jq to extract the assistant message and avoid printing the whole payload:
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":"Say hello in one word."}]}' \
| jq -r '.choices[0].message.content'
A successful call prints a single word like Hello. The full response looks like this:
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1710000000,
"model": "gpt-4o-mini",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hello"},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 10, "completion_tokens": 1, "total_tokens": 11}
}
Check finish_reason is stop rather than length to confirm the model completed naturally. The usage block is your per-token metering; log it if you need to track spend.
Step 4: Tune generation parameters
Control randomness and length by adding fields to the JSON body. Common knobs:
temperature(0–2): lower is more deterministic.max_tokens: hard cap on output tokens.top_p: nucleus sampling alternative to temperature.stop: array of strings that halt generation.
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",
"temperature": 0.2,
"max_tokens": 64,
"messages": [{"role": "user", "content": "List three RFC numbers."}]
}' | jq '.choices[0].message.content'
Setting max_tokens too low truncates answers; the API signals this with "finish_reason":"length". For reproducible tests, pin temperature to 0.
Step 5: Stream tokens over HTTP
For chat UIs you want incremental output. Set "stream": true and use curl -N to disable buffering:
curl -N https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"stream": true,
"messages": [{"role": "user", "content": "Write a haiku about TCP."}]
}'
The response is a server-sent event stream. Each line starts with data: and carries a JSON patch with choices[0].delta.content. The stream terminates with data: [DONE]. A quick shell flatten:
curl -N ... | grep -o '"content":"[^"]*"' | sed 's/"content":"//;s/"$//'
For anything serious, parse SSE in code. A minimal Python reader:
import json, urllib.request
req = urllib.request.Request(
"https://api.openai.com/v1/chat/completions",
data=json.dumps({
"model": "gpt-4o-mini",
"stream": True,
"messages": [{"role": "user", "content": "Count to 3."}]
}).encode(),
headers={"Authorization": "Bearer $OPENAI_API_KEY",
"Content-Type": "application/json"}
)
with urllib.request.urlopen(req) as r:
for line in r:
if line.startswith(b"data: "):
payload = line[6:].strip()
if payload == b"[DONE]":
break
delta = json.loads(payload)["choices"][0]["delta"]
if "content" in delta:
print(delta["content"], end="")
Step 6: Handle errors and rate limits
HTTP 429 means you hit a rate limit or quota. The body includes error.message and often error.type. A minimal retry loop in bash:
for i in 1 2 3; do
resp=$(curl -s -w "%{http_code}" 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":"hi"}]}')
code=${resp: -3}
if [ "$code" = "200" ]; then break; fi
sleep $((2**i))
done
If you route through a gateway like n4n.ai, automatic fallback when a provider is rate-limited or degraded removes the need for manual retry logic; with direct curl you must implement backoff yourself. Always log the x-request-id response header to trace failures with OpenAI support.
Step 7: Send system prompts and multi-turn context
The messages array accepts system, user, and assistant roles. System sets behavior; prior turns provide context:
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":"system","content":"You are a terse network engineer."},
{"role":"user","content":"What is MTU?"},
{"role":"assistant","content":"Maximum Transmission Unit, typically 1500 bytes on Ethernet."},
{"role":"user","content":"And on loopback?"}
]
}' | jq -r '.choices[0].message.content'
The model conditions on the full array order; do not reorder roles arbitrarily. Keep prior context under the model’s token window or you will get a 400 with a context length error.
Step 8: Wrap the call in a reusable script
A small function keeps your curl openai chat completions api invocations consistent:
chat() {
local prompt="$1"
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\":\"$prompt\"}]}" \
| jq -r '.choices[0].message.content'
}
Source it in .bashrc or a script. Escape double quotes in $prompt before production use; this example is deliberately minimal. For robust JSON building, use jq -n to construct the body instead of string concatenation.
Step 9: Request structured JSON output
When you need parseable output, set response_format and instruct the model explicitly:
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",
"response_format": {"type": "json_object"},
"messages": [{"role":"user","content":"Return JSON with keys: city, pop. Paris."}]
}' | jq '.choices[0].message.content | fromjson'
The model still must be told to emit JSON in the prompt; the flag only constrains the parser. Invalid schema returns a 400, so validate the prompt against the model’s known capabilities.
Step 10: Verify success end-to-end
Confirm three things after any call:
- HTTP status is 200 (use
-w "%{http_code}"or checkjqparse). choices[0].message.contentis non-empty.usage.total_tokensis greater than zero and within your budget.
A final smoke test suitable for CI:
code=$(curl -s -o /tmp/resp.json -w "%{http_code}" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}' \
https://api.openai.com/v1/chat/completions)
if [ "$code" = "200" ] && jq -e '.choices[0].message.content' /tmp/resp.json >/dev/null; then
echo "OK"
else
echo "FAIL: $code"
fi
That loop is the core of a scheduled check that alerts when the API key expires or the model is deprecated. The curl openai chat completions api pattern scales from a one-line terminal probe to a monitored production caller without changing the wire format.