Cost Tracking in Claude

Posted on Tuesday, March 10, 2026


 


Now that you are using Claude code how can you track and control your costs?

I have my own personal Claude set up at home and I want to make sure I do not overspend.  I put out on article covering how to set up an account with monthly limits
https://www.whiteboardcoder.com/2026/03/claude-creating-account-and-setting-up.html [1]

 

That article covers how to set up your account and set up a workspace.
I am going to assume you have a workspace and are accessing your account via the command line.   (I am also assuming you are running this in Ubuntu 24.04 if you are on OS X you may have to tweak some of the scripts)

 

Get the actuals

Let’s first create a curl just to make sure I can pull the data in I want.

What do you need?  You need admin privileges.  You cannot get your costs from a workspace API.  But since I am the boss of my account I can make this happen ๐Ÿ˜Š  But if you are running a larger or you probably want to put some kind of proxy in place for your


Let me create a new admin key
Head over to
https://platform.claude.com/ and login

 


 

Click API Keys

 


 

Click on Admin Keys

 


 

Click on Create Admin Key

 



Give it a name. And click Add


I am calling mine cost-key.  Currently an admin key has access to everything… I hope in the future they tweak that so you can limit a key to only access certain things like costs ๐Ÿ˜Š
But for now its all powerful so be careful!

 

 


Copy the key.

You will see that admin key names start with sk-ant-admin.
Make sure to copy this key someplace safe as you cannot get it again.  You can generate a new key but you can’t get this key again.


Claude has a cost_report api endpoint.  You can check their docs here https://platform.claude.com/docs/en/api/admin/cost_report/retrieve [2]

From that we can create a curl command to test ( I placed my key at ~/.claude-key-cost so that its simpler to ingest and I don’t want to put keys in my cmd history ๐Ÿ˜Š

 

> curl -s "https://api.anthropic.com/v1/organizations/cost_report?\
starting_at=2026-03-01T00:00:00Z&" \
  --header "anthropic-version: 2023-06-01" \
  --header "x-api-key: $(cat ~/.claude-key-cost)" | jq .


 


This will get me data from 3/1/26 from 00:00 UTC to the present time… Well it is paginated so you may have to call the next page.

 

You can also pass it an end date

 

> curl -s "https://api.anthropic.com/v1/organizations/cost_report?\
starting_at=2026-03-04T00:00:00Z&\
ending_at=2026-03-05T00:00:00Z&" \
  --header "anthropic-version: 2023-06-01" \
  --header "x-api-key: $(cat ~/.claude-key-cost)" | jq .


 


 

Here I just got one day back and you can see here I spent $0.15 that day.  The amount will be in your smallest local currency.  So for USA it’s in pennies.

 

You can also get the results by workspace id, which is what I want so I can get the cost per workspace I have.  Currently I only have one workspace, but in the future I plan to make a few more and I want all the info.

 

> curl -s "https://api.anthropic.com/v1/organizations/cost_report?\
group_by[]=workspace_id&\
starting_at=2026-03-04T00:00:00Z&\
ending_at=2026-03-05T00:00:00Z&" \
  --header "anthropic-version: 2023-06-01" \
  --header "x-api-key: $(cat ~/.claude-key-cost)" | jq .


 


 

That is nice but I really want the workspace name…

I don’t see a way to get the Workspace ID in the claude web portal.
Let me find a API to grab them.

Here it is https://platform.claude.com/docs/en/api/admin/workspaces/list [3]

 

> curl -s https://api.anthropic.com/v1/organizations/workspaces \
    -H 'anthropic-version: 2023-06-01' \
    -H "X-Api-Key: $(cat ~/.claude-key-cost)" | jq .


 


That worked now I can tie the ID into the name.

One more thing, I want to calculate my usage per month and what I have left over.  Looking up some info it looks like the month begins at UTC 00:00 time so… 2026-03-01T00:00:00Z (for march)

I do need to get my current workspaces monthly limit so I can calculate it….

After some research it looks like they do not yet have an API end point that pulls this info…. Dang  maybe in a few months..


So for now I can hard code a number and use that until I can get an API endpoint I can hit.

 

Let’s first create a simple command line tool to get the current monthly actuals.

 

> sudo vi /usr/bin/cost

 

Here is the code

 

#!/usr/bin/env python3
"""
Author: T. Patrick Bailey
        Whiteboarcoder.com
 
Returns the monthly cost/budget for Claude
Assumes an admin key is at ~/.claude-key-cost
and you have to hard code the BUDGET variable
"""
 
import os
import sys
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta
import requests
 
#AS of 3/9/26 you can't pull Budget limit from the API :(
#hardcodingit :(
BUDGET=20.00

# ------------------------------------------------
# Colors
# ------------------------------------------------
RED    = "\033[91m"
GREEN  = "\033[92m"
YELLOW = "\033[93m"
RESET  = "\033[0m"
 
def color_text(text: str, color: str) -> str:
    return f"{color}{text}{RESET}"
 
def color_days(days_left: int) -> str:
    text = f"Budget resets in {days_left} days"
    if days_left <= 5:
        return color_text(text, RED)
    if days_left <= 10:
        return color_text(text, YELLOW)
    return text
 
# ------------------------------------------------
# Load admin key
# ------------------------------------------------
def get_admin_key() -> str:
    key_path = os.path.expanduser("~/.claude-key-cost")
    if os.path.isfile(key_path) and os.access(key_path, os.R_OK):
        with open(key_path, "r") as f:
            content = f.read().strip()
        if content.startswith("sk-ant-admin"):
            return content
 
    print("Error: Admin key not found in env or ~/.claude-key-cost", file=sys.stderr)
    sys.exit(1)
 
# ------------------------------------------------
# Fetch workspace ID ฮ“รฅร† name map (paginated too, but usually small)
# ------------------------------------------------
def fetch_workspace_map(api_key: str) -> dict:
    headers = {
        "x-api-key": api_key,
        "anthropic-version": "2023-06-01",
        "Accept": "application/json",
    }
    url = "https://api.anthropic.com/v1/organizations/workspaces"
    params = {"limit": 100, "include_archived": "false"}
    mapping = {}
 
    while True:
        try:
            r = requests.get(url, headers=headers, params=params, timeout=15)
            r.raise_for_status()
            page = r.json()
        except requests.RequestException as e:
            print(f"Workspaces fetch failed: {e}", file=sys.stderr)
            return mapping
 
        for ws in page.get("data", []):
            ws_id = ws.get("id")
            name = ws.get("name") or ws.get("workspace_name")
            if ws_id and name:
                mapping[ws_id] = name
 
        if not page.get("has_more"):
            break
        params["after"] = page.get("next_page")  # or "page" depending on exact key
 
    return mapping
 
# ------------------------------------------------
# Fetch ALL cost report data with pagination
# ------------------------------------------------
def fetch_all_costs(api_key: str, start_iso: str, end_iso: str) -> list:
    headers = {
        "x-api-key": api_key,
        "anthropic-version": "2023-06-01",
        "Accept": "application/json",
    }
    url = "https://api.anthropic.com/v1/organizations/cost_report"
    params = {
        "starting_at": start_iso,
        "ending_at": end_iso,
        "group_by[]": "workspace_id",
        # Optional: "limit": 100,  # if supported; test with/without
    }
 
    all_results = []
    page_count = 0
    max_pages = 50  # safety net

 
    while page_count < max_pages:
        try:
            r = requests.get(url, headers=headers, params=params, timeout=30)
            r.raise_for_status()
            data = r.json()
        except requests.RequestException as e:
            print(f"Cost report page {page_count+1} failed: {e}", file=sys.stderr)
            if 'r' in locals():
                print(r.text, file=sys.stderr)
            break
 
        for d in data.get("data", []):
           results = d.get("results", [])
           #This will skip empty results (days in which nothing happens
           if results:
             all_results.extend(results)
 
        if not data.get("has_more"):
            break 
        next_token = data.get("next_page")
        if not next_token:
            break 

        params["page"] = next_token   # docs use "page=..." for next requests 

        page_count += 1
     if page_count >= max_pages:
        print("Warning: Hit max pages safety limit ฮ“ร‡รถ data may be incomplete", file=sys.stderr) 

    return all_results

# ------------------------------------------------
# Main
# ------------------------------------------------
def main():
    api_key = get_admin_key()
    ws_map = fetch_workspace_map(api_key)
 
    now = datetime.now(timezone.utc)
    start_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
    next_month = start_month + relativedelta(months=1)
    end_month = next_month - relativedelta(microseconds=1) 

    days_in_month = (end_month - start_month).days + 1
    days_left = days_in_month - (now - start_month).days 

    all_results = fetch_all_costs(
        api_key,
        start_month.isoformat(),
        now.isoformat()  # up to now, not end of month
    )

    spent_arr = {}
    for result in all_results:
        ws_id = result.get("workspace_id")
        if not ws_id:
            continue
        name = ws_map.get(ws_id, ws_id[:12] + "ฮ“ร‡ยช" if len(ws_id) > 12 else ws_id)

 
        cost = float(result.get("amount", 0))
        spent_arr[name] = spent_arr.get(name, 0.0) + cost

     # Output
    print(f"\nClaude Monthly Usage Report ฮ“ร‡รถ {start_month.strftime('%Y-%m')}")
    print(f"  As of: {now.strftime('%Y-%m-%d %H:%M UTC')}")
    print(f"  Days left: {days_left}")
    print("ฮ“รถร‡" * 60)

     if not spent_arr:
        print("No data or budgets configured.")
        return   

    for workspace in spent_arr:
      spent=spent_arr[workspace]/100
      remain = BUDGET - spent
      pct = (spent / BUDGET * 100) if BUDGET > 0 else 0 

      #Under 15% left
      remain_color = GREEN if pct <= 85 else RED
      print(f"Workspace: {workspace}")
      print(f"  Budget:       ${BUDGET:8,.2f}")
      print(f"  Spent:        ${spent:8,.2f} ({pct:5.1f}%)")
      print(f"  Remaining:    {color_text(f'${remain:8,.2f}', remain_color)}")
      print(f"  {color_days(days_left)}")
      print() 

if __name__ == "__main__":
    main()


 

Or download the code from this gist https://gist.github.com/patmandenver/0001b0ac2500b7bfe69eaa92a8988d64


Change the permissions

 

> sudo chmod 755 /usr/bin/cost

 


Now let’s try it.

 

> cost

 


 

OK that gets me what I want.  This gets every workspace I have.  For now that works.  

In the near future I would like to create a tool so that any person from a workspace could access their workspace’s current monthly budget.

If I had to do that today I would have to make some API tool that others could query like an AWS lambda.  But I don’t want to put my admin key in an AWS lambda function… too much power.


So hopefully in a few months, Ideally a person who has permissions into a workspace with their workspace API should be able to query this with the workspace key.

 

Here is hoping ๐Ÿ˜Š   

References

 

[1]       Claude creating an account and setting up limits
             
https://www.whiteboardcoder.com/2026/03/claude-creating-account-and-setting-up.html
            Accessed 03/2026
[2]       Claude CostReport API
             
https://platform.claude.com/docs/en/api/admin/cost_report/retrieve
            Accessed 03/2026
[3]       List Workspaces API
             
https://platform.claude.com/docs/en/api/admin/workspaces/list
            Accessed 03/2026

 

No comments:

Post a Comment