n4nAI

Logging LLM API costs from a bash automation script

Learn how to implement bash log llm api cost tracking in shell automation scripts with OpenAI-compatible endpoints and per-token metering.

n4n Team3 min read586 words

Audio narration

Coming soon — every post will get a voice note here.

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.

Tagsbashcost-trackingautomationlogging

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All bash/shell scripting llm automation posts →