<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>pven</title>
    <link>https://unprompted.pven.com/</link>
    <description>Homelab notes on Claude, automation, and self-hosted tools.</description>
    <pubDate>Sat, 23 May 2026 21:09:02 +0000</pubDate>
    <item>
      <title>Monitoring Claude Pro Usage with a Bash Script</title>
      <link>https://unprompted.pven.com/monitoring-claude-pro-usage-with-a-bash-script</link>
      <description>&lt;![CDATA[If you run Claude Code heavily, you&#39;ve probably stared at the usage bar wondering how much of your 5-hour block is gone — and whether your extra credits are quietly draining. I built a small bash script to stop guessing and start logging.&#xA;&#xA;The problem&#xA;&#xA;Claude Pro has three usage dimensions worth tracking:&#xA;&#xA;5-hour block utilisation — the rolling window that gates your active sessions&#xA;7-day utilisation — the broader weekly cap&#xA;Extra credits — the paid overflow, billed in euros&#xA;&#xA;None of this is exposed in a machine-readable way by default. It turns out there is an undocumented OAuth endpoint that exposes exactly that data.&#xA;&#xA;The endpoint&#xA;&#xA;Claude Code stores an OAuth access token in ~/.claude/.credentials.json. The same token works against the usage API:&#xA;&#xA;GET https://api.anthropic.com/api/oauth/usage&#xA;Authorization: Bearer token&#xA;anthropic-beta: oauth-2025-04-20&#xA;&#xA;The response looks like this:&#xA;&#xA;{&#xA;  &#34;fivehour&#34;: { &#34;utilization&#34;: 7.0, &#34;resetsat&#34;: &#34;...&#34; },&#xA;  &#34;sevenday&#34;: { &#34;utilization&#34;: 19.0, &#34;resetsat&#34;: &#34;...&#34; },&#xA;  &#34;extrausage&#34;: {&#xA;    &#34;isenabled&#34;: true,&#xA;    &#34;monthlylimit&#34;: 1700,&#xA;    &#34;usedcredits&#34;: 190.0,&#xA;    &#34;utilization&#34;: 11.18,&#xA;    &#34;currency&#34;: &#34;EUR&#34;&#xA;  }&#xA;}&#xA;&#xA;Note: monthlylimit and usedcredits are in eurocents. Divide by 100 for the actual euro amounts.&#xA;&#xA;The script&#xA;&#xA;The script reads the token, hits the endpoint, optionally queries ccusage blocks for remaining block time, and appends a single JSON line to a log file — ready for Loki to scrape and Grafana to visualise.&#xA;&#xA;!/usr/bin/env bash&#xA;Dependencies: jq, curl, ccusage (npm install -g ccusage)&#xA;&#xA;LOGFILE=&#34;/var/log/ccusage.log&#34;&#xA;CREDENTIALSFILE=&#34;${HOME}/.claude/.credentials.json&#34;&#xA;ANTHROPICUSAGEURL=&#34;https://api.anthropic.com/api/oauth/usage&#34;&#xA;&#xA;log() { echo &#34;[$(date &#39;+%d-%b-%Y %H:%M:%S&#39;)] $&#34;   &amp;2; }&#xA;&#xA;TOKEN=$(jq -r &#39;.claudeAiOauth.accessToken // empty&#39; &#34;$CREDENTIALSFILE&#34; 2  /dev/null)&#xA;if [[ -z &#34;$TOKEN&#34; ]]; then&#xA;    log &#34;No credentials available, aborting&#34;&#xA;    exit 1&#xA;fi&#xA;&#xA;USAGE=$(curl -s \&#xA;    -H &#34;Authorization: Bearer $TOKEN&#34; \&#xA;    -H &#34;anthropic-beta: oauth-2025-04-20&#34; \&#xA;    &#34;$ANTHROPICUSAGEURL&#34;)&#xA;&#xA;if ! echo &#34;$USAGE&#34; | jq -e &#39;.fivehour&#39;   /dev/null 2  &amp;1; then&#xA;    log &#34;Invalid API response&#34;&#xA;    exit 1&#xA;fi&#xA;&#xA;BLOK=$(echo &#34;$USAGE&#34;         | jq &#39;(.fivehour.utilization   // 0) | round&#39;)&#xA;WEEK=$(echo &#34;$USAGE&#34;         | jq &#39;(.sevenday.utilization   // 0) | round&#39;)&#xA;CREDITSPCT=$(echo &#34;$USAGE&#34;  | jq &#39;(.extrausage.utilization // 0)  100 | round / 100&#39;)&#xA;CREDITSUSED=$(echo &#34;$USAGE&#34; | jq &#39;(.extrausage.usedcredits  // 0) / 100&#39;)&#xA;CREDITSMAX=$(echo &#34;$USAGE&#34;  | jq &#39;(.extrausage.monthlylimit // 0) / 100&#39;)&#xA;&#xA;REMAINING=$(ccusage blocks --json 2  /dev/null | jq &#39;&#xA;    if (.blocks // []) | length   0&#xA;       and (.blocks[-1].projection.remainingMinutes // null) != null&#xA;    then (.blocks[-1].projection.remainingMinutes / 60  10 | round / 10)&#xA;    else 0&#xA;    end&#39; 2  /dev/null || echo 0)&#xA;&#xA;printf &#39;{&#34;timestamp&#34;:&#34;%s&#34;,&#34;blokpct&#34;:%s,&#34;weekpct&#34;:%s,&#34;creditspct&#34;:%s,&#34;creditsused&#34;:%s,&#34;creditsmax&#34;:%s,&#34;blokremaininghours&#34;:%s}\n&#39; \&#xA;    &#34;$(date -u &#39;+%Y-%m-%dT%H:%M:%SZ&#39;)&#34; \&#xA;    &#34;$BLOK&#34; &#34;$WEEK&#34; &#34;$CREDITSPCT&#34; &#34;$CREDITSUSED&#34; &#34;$CREDITSMAX&#34; &#34;$REMAINING&#34; \&#xA;        &#34;$LOGFILE&#34;&#xA;&#xA;log &#34;ccusage logged: block=${BLOK}% week=${WEEK}% credits=${CREDITSPCT}%&#34;&#xA;&#xA;Install&#xA;&#xA;Dependencies:&#xA;&#xA;apt install jq curl&#xA;npm install -g ccusage&#xA;&#xA;Deploy:&#xA;&#xA;cp ccusagelog.sh /usr/local/sbin/ccusagelog&#xA;chmod +x /usr/local/sbin/ccusagelog&#xA;&#xA;Cron — every 5 minutes:&#xA;&#xA;/5     /usr/local/sbin/ccusagelog&#xA;&#xA;Output&#xA;&#xA;Each run appends one line to /var/log/ccusage.log:&#xA;&#xA;{&#34;timestamp&#34;:&#34;2026-05-23T19:26:17Z&#34;,&#34;blokpct&#34;:7,&#34;weekpct&#34;:19,&#34;creditspct&#34;:11.18,&#34;creditsused&#34;:1.9,&#34;creditsmax&#34;:17,&#34;blokremaininghours&#34;:0}&#xA;&#xA;Point a Loki scrape job at that file and you have time-series data for all six metrics. Setting up Loki and Grafana for this is a topic for a separate post.&#xA;&#xA;Notes&#xA;&#xA;Tested on Claude Pro with Claude Code. May work with other Claude subscriptions, but I haven&#39;t verified.&#xA;The OAuth endpoint is undocumented and could change without notice.&#xA;blokremaininghours is 0 when no active session block is running — that&#39;s expected.&#xA;The full script with the latest version is available on GitHub: pven/scripts/claude&#xA;&#xA;---&#xA;Written with assistance from Claude.&#xA;&#xA;---&#xD;&#xA;Want to reach out? Contact me._]]&gt;</description>
      <content:encoded><![CDATA[<p>If you run Claude Code heavily, you&#39;ve probably stared at the usage bar wondering how much of your 5-hour block is gone — and whether your extra credits are quietly draining. I built a small bash script to stop guessing and start logging.</p>

<h2 id="the-problem">The problem</h2>

<p>Claude Pro has three usage dimensions worth tracking:</p>
<ul><li><strong>5-hour block utilisation</strong> — the rolling window that gates your active sessions</li>
<li><strong>7-day utilisation</strong> — the broader weekly cap</li>
<li><strong>Extra credits</strong> — the paid overflow, billed in euros</li></ul>

<p>None of this is exposed in a machine-readable way by default. It turns out there is an undocumented OAuth endpoint that exposes exactly that data.</p>

<h2 id="the-endpoint">The endpoint</h2>

<p>Claude Code stores an OAuth access token in <code>~/.claude/.credentials.json</code>. The same token works against the usage API:</p>

<pre><code>GET https://api.anthropic.com/api/oauth/usage
Authorization: Bearer &lt;token&gt;
anthropic-beta: oauth-2025-04-20
</code></pre>

<p>The response looks like this:</p>

<pre><code class="language-json">{
  &#34;five_hour&#34;: { &#34;utilization&#34;: 7.0, &#34;resets_at&#34;: &#34;...&#34; },
  &#34;seven_day&#34;: { &#34;utilization&#34;: 19.0, &#34;resets_at&#34;: &#34;...&#34; },
  &#34;extra_usage&#34;: {
    &#34;is_enabled&#34;: true,
    &#34;monthly_limit&#34;: 1700,
    &#34;used_credits&#34;: 190.0,
    &#34;utilization&#34;: 11.18,
    &#34;currency&#34;: &#34;EUR&#34;
  }
}
</code></pre>

<p>Note: <code>monthly_limit</code> and <code>used_credits</code> are in eurocents. Divide by 100 for the actual euro amounts.</p>

<h2 id="the-script">The script</h2>

<p>The script reads the token, hits the endpoint, optionally queries <code>ccusage blocks</code> for remaining block time, and appends a single JSON line to a log file — ready for Loki to scrape and Grafana to visualise.</p>

<pre><code class="language-bash">#!/usr/bin/env bash
# Dependencies: jq, curl, ccusage (npm install -g ccusage)

LOGFILE=&#34;/var/log/ccusage.log&#34;
CREDENTIALS_FILE=&#34;${HOME}/.claude/.credentials.json&#34;
ANTHROPIC_USAGE_URL=&#34;https://api.anthropic.com/api/oauth/usage&#34;

log() { echo &#34;[$(date &#39;+%d-%b-%Y %H:%M:%S&#39;)] $*&#34; &gt;&amp;2; }

TOKEN=$(jq -r &#39;.claudeAiOauth.accessToken // empty&#39; &#34;$CREDENTIALS_FILE&#34; 2&gt;/dev/null)
if [[ -z &#34;$TOKEN&#34; ]]; then
    log &#34;No credentials available, aborting&#34;
    exit 1
fi

USAGE=$(curl -s \
    -H &#34;Authorization: Bearer $TOKEN&#34; \
    -H &#34;anthropic-beta: oauth-2025-04-20&#34; \
    &#34;$ANTHROPIC_USAGE_URL&#34;)

if ! echo &#34;$USAGE&#34; | jq -e &#39;.five_hour&#39; &gt; /dev/null 2&gt;&amp;1; then
    log &#34;Invalid API response&#34;
    exit 1
fi

BLOK=$(echo &#34;$USAGE&#34;         | jq &#39;(.five_hour.utilization   // 0) | round&#39;)
WEEK=$(echo &#34;$USAGE&#34;         | jq &#39;(.seven_day.utilization   // 0) | round&#39;)
CREDITS_PCT=$(echo &#34;$USAGE&#34;  | jq &#39;(.extra_usage.utilization // 0) * 100 | round / 100&#39;)
CREDITS_USED=$(echo &#34;$USAGE&#34; | jq &#39;(.extra_usage.used_credits  // 0) / 100&#39;)
CREDITS_MAX=$(echo &#34;$USAGE&#34;  | jq &#39;(.extra_usage.monthly_limit // 0) / 100&#39;)

REMAINING=$(ccusage blocks --json 2&gt;/dev/null | jq &#39;
    if (.blocks // []) | length &gt; 0
       and (.blocks[-1].projection.remainingMinutes // null) != null
    then (.blocks[-1].projection.remainingMinutes / 60 * 10 | round / 10)
    else 0
    end&#39; 2&gt;/dev/null || echo 0)

printf &#39;{&#34;timestamp&#34;:&#34;%s&#34;,&#34;blok_pct&#34;:%s,&#34;week_pct&#34;:%s,&#34;credits_pct&#34;:%s,&#34;credits_used&#34;:%s,&#34;credits_max&#34;:%s,&#34;blok_remaining_hours&#34;:%s}\n&#39; \
    &#34;$(date -u &#39;+%Y-%m-%dT%H:%M:%SZ&#39;)&#34; \
    &#34;$BLOK&#34; &#34;$WEEK&#34; &#34;$CREDITS_PCT&#34; &#34;$CREDITS_USED&#34; &#34;$CREDITS_MAX&#34; &#34;$REMAINING&#34; \
    &gt;&gt; &#34;$LOGFILE&#34;

log &#34;ccusage logged: block=${BLOK}% week=${WEEK}% credits=${CREDITS_PCT}%&#34;
</code></pre>

<h2 id="install">Install</h2>

<p><strong>Dependencies:</strong></p>

<pre><code class="language-bash">apt install jq curl
npm install -g ccusage
</code></pre>

<p><strong>Deploy:</strong></p>

<pre><code class="language-bash">cp ccusage_log.sh /usr/local/sbin/ccusage_log
chmod +x /usr/local/sbin/ccusage_log
</code></pre>

<p><strong>Cron — every 5 minutes:</strong></p>

<pre><code class="language-bash">*/5 * * * * /usr/local/sbin/ccusage_log
</code></pre>

<h2 id="output">Output</h2>

<p>Each run appends one line to <code>/var/log/ccusage.log</code>:</p>

<pre><code class="language-json">{&#34;timestamp&#34;:&#34;2026-05-23T19:26:17Z&#34;,&#34;blok_pct&#34;:7,&#34;week_pct&#34;:19,&#34;credits_pct&#34;:11.18,&#34;credits_used&#34;:1.9,&#34;credits_max&#34;:17,&#34;blok_remaining_hours&#34;:0}
</code></pre>

<p>Point a Loki scrape job at that file and you have time-series data for all six metrics. Setting up Loki and Grafana for this is a topic for a separate post.</p>

<h2 id="notes">Notes</h2>
<ul><li>Tested on Claude Pro with Claude Code. May work with other Claude subscriptions, but I haven&#39;t verified.</li>
<li>The OAuth endpoint is undocumented and could change without notice.</li>
<li><code>blok_remaining_hours</code> is <code>0</code> when no active session block is running — that&#39;s expected.</li>
<li>The full script with the latest version is available on GitHub: <a href="https://github.com/pven/scripts/tree/main/claude">pven/scripts/claude</a></li></ul>

<hr>

<p><em>Written with assistance from Claude.</em></p>

<hr>

<p><em>Want to reach out? <a href="https://contact.pven.com">Contact me</a>.</em></p>
]]></content:encoded>
      <guid>https://unprompted.pven.com/monitoring-claude-pro-usage-with-a-bash-script</guid>
      <pubDate>Sat, 23 May 2026 17:39:44 +0000</pubDate>
    </item>
  </channel>
</rss>