Calling the Aggregator From Pure Bash and Makefiles Without Any SDK
A practical guide to calling an LLM API aggregator directly from Bash scripts and Makefiles without any SDK, covering HTTP requests, authentication, JSON handling, and Makefile integration.
Why Skip the SDK?
Sometimes you just want to call an API from a shell script or Makefile without pulling in a language-specific SDK. Maybe you're in a minimal container, writing a build step, or automating a quick task. This guide shows how to interact with an LLM API aggregator using only standard command-line tools.
Prerequisites
You'll need:
curlfor making HTTP requestsjqfor parsing JSON responses (optional but helpful)- An API key from your aggregator
- The base URL of the aggregator's API endpoint
Most aggregators provide an OpenAI-compatible endpoint, which means you can use the same request format as OpenAI's chat completions API. Check your aggregator's documentation for the exact base URL and authentication method.
Making a Basic Request with curl
The core of any API call is an HTTP POST request. Here's a minimal example using curl:
curl https://api.aggregator.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"model": "claude-3-5-sonnet",
"messages": [{"role": "user", "content": "Hello, world!"}]
}'
Key points:
- Replace the URL with your aggregator's actual endpoint.
- Store your API key in an environment variable (
$API_KEY) to avoid hardcoding it. - The
-dflag sends the JSON payload. - The response will be JSON; you can pipe it to
jqfor readability.
Parsing the Response
Without jq, the raw JSON can be hard to read. Use jq to extract the assistant's reply:
response=$(curl -s https://api.aggregator.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"model": "claude-3-5-sonnet",
"messages": [{"role": "user", "content": "Hello, world!"}]
}')
echo "$response" | jq -r '.choices[0].message.content'
If jq isn't available, you can use grep/sed for simple extraction, but jq is more robust.
Building a Reusable Bash Function
Encapsulate the API call in a function for reuse:
ask() {
local prompt="$1"
local model="${2:-claude-3-5-sonnet}"
curl -s https://api.aggregator.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d "{
\"model\": \"$model\",
\"messages\": [{\"role\": \"user\", \"content\": \"$prompt\"}]
}" | jq -r '.choices[0].message.content'
}
ask "What is the capital of France?"
Note: This simple function doesn't handle escaping of quotes in the prompt. For production use, use jq -n to safely build JSON.
Safer JSON Construction with jq
To avoid injection and escaping issues, build the JSON payload with jq:
payload=$(jq -n \
--arg model "claude-3-5-sonnet" \
--arg content "$prompt" \
'{model: $model, messages: [{role: "user", content: $content}]}')
curl -s https://api.aggregator.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d "$payload" | jq -r '.choices[0].message.content'
This approach handles special characters correctly.
Integrating with Makefiles
Makefiles are great for automating build steps, and you can call the API from a make target. Here's an example that generates a summary of a file:
API_KEY ?= $(shell printenv API_KEY)
ENDPOINT = https://api.aggregator.com/v1/chat/completions
MODEL = claude-3-5-sonnet
summarize:
@payload=$$(jq -n \
--arg model "$(MODEL)" \
--arg content "Summarize this: $$(cat input.txt)" \
'{model: $$model, messages: [{role: "user", content: $$content}]}'); \
curl -s $(ENDPOINT) \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $(API_KEY)" \
-d "$$payload" | jq -r '.choices[0].message.content'
Key points for Makefiles:
- Use
$$for shell variables to prevent make from expanding them. - Use
?=for API_KEY so it can be overridden from the environment or command line. - Each line in a recipe runs in its own shell, so combine commands with
\and;.
Handling Errors and Rate Limits
Always check the HTTP status code. curl's -f flag makes it fail silently on server errors, but you can capture the status:
http_code=$(curl -s -o response.json -w "%{http_code}" ... )
if [ "$http_code" -ne 200 ]; then
echo "Error: HTTP $http_code" >&2
cat response.json >&2
exit 1
fi
For rate limits (HTTP 429), implement a simple retry with backoff in your script. The aggregator may also return rate limit headers; check the documentation.
Security Considerations
- Never hardcode API keys in scripts or Makefiles committed to version control. Use environment variables or a secrets manager.
- Be cautious when including user input in API calls; always sanitize and use proper JSON encoding.
- Consider setting a timeout with
curl --max-timeto avoid hanging scripts.
Conclusion
You don't need an SDK to call an LLM API aggregator. With curl, jq, and a bit of shell scripting, you can integrate AI capabilities into your Bash scripts and Makefiles. This approach is lightweight, portable, and works in minimal environments. Just remember to handle errors, secure your API key, and construct JSON safely.