DEV Community

Cover image for How I Built a Chrome Extension That Reads Claude's Internal API
Anoop Kumar
Anoop Kumar

Posted on

How I Built a Chrome Extension That Reads Claude's Internal API

When I started building TokenPulse, I assumed I would need an API key to read Claude's rate limit data. I was wrong. Claude exposes exact utilization percentages and reset timestamps through an internal endpoint that your browser session already has access to — because Claude's own interface uses it.

Here is exactly how it works and how I built the integration.

Discovering the endpoint

The first step was understanding what data Claude's interface actually requests. I opened DevTools → Network tab, filtered by XHR and Fetch, and started a conversation with Claude while watching the requests.

Within a few messages, I spotted a request to an endpoint returning usage data:

{
  "raw_limits": {
    "five_hour": {
      "remaining_tokens": 18432,
      "total_tokens": 90000,
      "reset_at": "2026-07-15T14:14:00.000Z"
    },
    "seven_day": {
      "remaining_tokens": 58900,
      "total_tokens": 90000,
      "reset_at": "2026-07-21T21:00:00.000Z"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The browser already had permission to read this because the session cookie was included automatically. No API key needed.

Intercepting the response in a content script

Content scripts run in an isolated world — they cannot directly intercept fetch calls made by the page's JavaScript. The solution is injecting a script into the page's actual JavaScript context:

// content-script.js
function injectPageScript() {
  const script = document.createElement('script')
  script.src = chrome.runtime.getURL('injected.js')
  script.onload = () => script.remove()
  ;(document.head || document.documentElement).appendChild(script)
}

injectPageScript()
Enter fullscreen mode Exit fullscreen mode

The injected script runs in page context and intercepts fetch calls:

// injected.js
const originalFetch = window.fetch

window.fetch = async function(...args) {
  const response = await originalFetch.apply(this, args)
  const url = typeof args[0] === 'string' ? args[0] : args[0].url

  if (url.includes('/api/usage') || url.includes('/rate_limits')) {
    const clone = response.clone()
    clone.json().then(data => {
      window.dispatchEvent(new CustomEvent('tp-usage-data', {
        detail: data
      }))
    }).catch(() => {})
  }

  return response
}
Enter fullscreen mode Exit fullscreen mode

The content script listens for the custom event:

window.addEventListener('tp-usage-data', (event) => {
  processUsageData(event.detail)
})
Enter fullscreen mode Exit fullscreen mode

Parsing and displaying the data

function parseRateLimits(raw) {
  const now = Date.now()

  function parseWindow(w) {
    if (!w) return null
    const total = w.total_tokens || 90000
    const remaining = w.remaining_tokens || total
    const used = total - remaining
    const resetAt = w.reset_at ? new Date(w.reset_at).getTime() : null
    const minutesUntilReset = resetAt
      ? Math.floor(Math.max(0, resetAt - now) / 60000)
      : null

    return {
      utilization: Math.min(1, used / total),
      minutesUntilReset,
      resetAt,
    }
  }

  return {
    five_hour: parseWindow(raw?.raw_limits?.five_hour),
    seven_day: parseWindow(raw?.raw_limits?.seven_day),
  }
}

function formatCountdown(minutes) {
  if (!minutes || minutes <= 0) return 'Resetting...'
  const h = Math.floor(minutes / 60)
  const m = minutes % 60
  if (h === 0) return `${m}m`
  if (m === 0) return `${h}h`
  return `${h}h ${m}m`
}
Enter fullscreen mode Exit fullscreen mode

The MV3 manifest requirement

For the injected script to load, declare it in web_accessible_resources:

{
  "web_accessible_resources": [{
    "resources": ["injected.js", "icons/icon48.png"],
    "matches": ["https://claude.ai/*"]
  }]
}
Enter fullscreen mode Exit fullscreen mode

Without this the injection silently fails.

What does not work and why

Direct fetch from service worker — fails with CORS because session cookies are not sent cross-origin from the service worker context.

Reading cookies directly — Claude's session tokens are HttpOnly, so JavaScript cannot read them.

The page-context injection pattern is the only reliable approach for intercepting authenticated requests made by the page's own JavaScript.


Full implementation at github.com/anu-ship-it/TokenPulse.
TokenPulse is free — tracks Claude, ChatGPT, Gemini, DeepSeek and Grok.

Top comments (0)