Shell automation often calls language models without any visibility into spend. This guide shows how to build bash log llm api cost tracking directly into your cron jobs and CI pipelines using OpenAI-compatible endpoints and response metadata.
Step 1: Make a safe API request and capture usage
Never interpolate user text directly into a JSON string with shell quotes. Use jq to build the payload, then call the endpoint with curl. Store both the HTTP status and the body.
API_BASE="${LLM_API_BASE:-https://api.openai.com/v1}"
API_KEY="$LLM_API_KEY"
MODEL="gpt-4o-mini"
PROMPT="Summarize: $INPUT_TEXT"
payload=$(jq -n --arg model "$MODEL" --arg content "$PROMPT" \
'{model:$model, messages:[{role:"user",content:$content}], temperature:0}')
read -r http_code resp < <(curl -s -w "\n%{http_code}" "$API_BASE/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "$payload")
If you route through a unified gateway, the request shape is identical. An OpenAI-compatible gateway such as n4n.ai returns per-token usage metering in the standard usage object, so your parsing layer stays constant across 240+ models.
Step 2: Parse token counts and the actual model
Extract the fields you need. The returned .model may not match your request when a gateway applies automatic fallback under rate limits or degradation.
if [ "$http_code" != "200" ]; then
echo "$(date -u) HTTP $http_code" >> /var/log/llm_errors.log
exit 1
fi
model=$(echo "$resp" | jq -r .model)
pin=$(echo "$resp" | jq -r .usage.prompt_tokens)
pout=$(echo "$resp" | jq -r .usage.completion_tokens)
cached=$(echo "$resp" | jq -r '.usage.prompt_tokens_details.cached_tokens // 0')
Log the returned model, not the requested one, or your price lookup will be wrong. The cached count is essential if you forward provider cache-control hints; cached tokens often bill at a lower rate.
Step 3: Define a price table
Bash lacks floating point. Store rates as integer tenths of a cent per 1K tokens, or use bc later. Public list prices change; keep this in a separate file you can update.
declare -A INPUT_PRICE # cents per 1K input tokens
declare -A OUTPUT_PRICE # cents per 1K output tokens
INPUT_PRICE["gpt-4o-mini"]=1
OUTPUT_PRICE["gpt-4o-mini"]=3
INPUT_PRICE["gpt-4o"]=5
OUTPUT_PRICE["gpt-4o"]=15
CACHED_DISCOUNT=0.1 # cached tokens cost 10% of normal input rate
If your gateway honors client routing directives and forwards provider cache-control hints, set Cache-Control: max-age=3600 on repeated prompts to populate cached_tokens. Track that discount explicitly.
Step 4: Compute cost with bc
Write a function that returns cost in cents.
compute_cost() {
local model=$1 pin=$2 pout=$3 cached=$4
local in_rate=${INPUT_PRICE[$model]:-0}
local out_rate=${OUTPUT_PRICE[$model]:-0}
local cached_rate=$(echo "scale=4; $in_rate * $CACHED_DISCOUNT" | bc)
echo "scale=6; (($pin - $cached) * $in_rate + $cached * $cached_rate + $pout * $out_rate) / 1000" | bc
}
This subtracts cached tokens from billed input and applies the discount. The result is cents; divide by 100 for dollars in reports.
Step 5: Append a CSV log line
Keep one row per call with UTC timestamp.
COST_LOG="${COST_LOG:-/var/log/llm_cost.csv}"
log_cost() {
local ts=$1 model=$2 pin=$3 pout=$4 cached=$5 cost=$6
echo "$ts,$model,$pin,$pout,$cached,$cost" >> "$COST_LOG"
}
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
cost=$(compute_cost "$model" "$pin" "$pout" "$cached")
log_cost "$ts" "$model" "$pin" "$pout" "$cached" "$cost"
The pattern for bash log llm api cost tracking is now complete at the call site. Wrap it in a function so every script shares the same accounting.
Step 6: Build a reusable wrapper script
Create /usr/local/bin/llm_call.sh:
#!/usr/bin/env bash
set -euo pipefail
# source price table
source /etc/llm_prices.sh
COST_LOG="${COST_LOG:-/var/log/llm_cost.csv}"
compute_cost() { /* as above */ }
log_cost() { /* as above */ }
main() {
local input=$1
# build payload, curl, parse, compute, log
# echo only the model text response to stdout
echo "$resp" | jq -r .choices[0].message.content
}
main "$@"
Your automation now calls llm_call.sh and gets the completion on stdout while side-effecting the cost log. Rotate /var/log/llm_cost.csv with logrotate weekly.
Step 7: Harden for production cron
Robust bash log llm api cost tracking requires handling non-200, timeouts, and missing usage. Add a timeout to curl:
curl -s --max-time 30 -w "\n%{http_code}" ...
In cron, export keys via a protected file:
0 * * * * root . /etc/llm_env.sh; /usr/local/bin/llm_call.sh "$(cat /tmp/job.txt)" > /tmp/out.txt 2>>/var/log/llm_cron.log
If the gateway returns 200 with a fallback model, your Step 2 extraction already captured the real id. No extra code needed.
Step 8: Verify success
Run the wrapper with a trivial prompt and inspect the log:
/usr/local/bin/llm_call.sh "ping" >/dev/null
tail -n 1 /var/log/llm_cost.csv
# 2025-03-14T12:00:00Z,gpt-4o-mini,3,1,0,0.006000
For gpt-4o-mini at 1 cent/1K in and 3 cent/1K out, 3 input + 1 output = (31+13)/1000 = 0.006 cents. The number matches. Sum daily spend with awk:
awk -F, 'BEGIN{s=0} {s+=$6} END{printf "%.4f cents\n", s}' /var/log/llm_cost.csv
If the cost column is zero or missing, check that jq parsed usage and that your price table contains the returned model key.
Why not just use the provider dashboard
Dashboards aggregate after the fact and rarely attribute cost to a specific cron task. Inline bash log llm api cost tracking gives per-invocation granularity, lets you alert when a single job exceeds a budget, and works identically across providers behind one OpenAI-compatible endpoint. Maintain one price file, parse one usage shape, and you have auditable spend data in plain text.
Caveats
Token counts are integers; cost is derived. If a provider changes pricing mid-month, your historical log stays correct only if you version the price table. For cached tokens, not all models report prompt_tokens_details; default to zero. When you need exact billed amounts, reconcile the CSV against the gateway’s per-token usage metering export at month end.