Your Claude Code Token on Windows: Account Usage vs API Keys, and How to Read It
If you use Claude Code inside VS Code on a paid plan, you are already authenticated - but with a credential most people never see. This article explains which credential it is, how it differs from an API key, and how to print it yourself on Windows. (A follow-up article uses this token to stream the model's thinking; read this one first.)
Two completely different ways to pay Claude - and two different tokens
There are two independent billing/auth models, and they use different credentials:
- API usage (pay-as-you-go). You create an API key (
sk-ant-api...) in the Claude Console. Every token is billed to your API account. This is what you use when you callhttps://api.anthropic.com/v1/messagesfrom your own code the "normal" way, with anx-api-keyheader. - Account (subscription) usage. When you sign into Claude Code with your Claude.ai login (Pro / Max / Team / Enterprise), it runs an OAuth flow in the browser and stores an OAuth access token (a bearer token) plus a refresh token. Requests made with that bearer draw on your subscription's usage limits - the same pool as Claude.ai chat - not API billing.
Free accounts don't get this. Claude Code's subscription auth requires a paid plan; a free Claude.ai account has no Claude Code inference access, so there's no bearer to retrieve. (Confidence: high on "paid plan required"; I verified the working case on a Pro account, not the free-account rejection directly.) You can tell which world you're in: an sk-ant-api... key is API billing; the OAuth credential below (with a subscriptionType like pro/max) is account usage.
The auth layer: bearer token vs session token
Claude Code's OAuth credential has two tokens that do different jobs:
- Bearer token = the OAuth access token. It's what you actually send on a request:
Authorization: Bearer <accessToken>. It's short-lived (hours). When it expires, calls 401. - Session token = the OAuth refresh token. It's long-lived (weeks) and represents your logged-in session. Claude Code uses it to silently mint a fresh access token when the old one expires - which is why you rarely re-login. Treat it like a password: anyone holding it can mint bearers as you.
On this machine the two tokens had these lifetimes (from an actual run): the bearer expired in about 7.5 hours, the refresh/session token in about 29 days.
Where Windows keeps them
On Windows, Claude Code stores the credential as plaintext JSON:
%USERPROFILE%\.claude\.credentials.json
Its shape (values redacted):
{
"claudeAiOauth": {
"accessToken": "sk-ant-oat...", // BEARER token (Authorization: Bearer ...)
"refreshToken": "sk-ant-ort...", // SESSION token (mints new bearers)
"expiresAt": 1755881332000, // accessToken expiry, ms since epoch
"refreshTokenExpiresAt": 1758406407000,
"scopes": ["user:inference", "user:profile", "user:sessions:claude_code", "..."],
"subscriptionType": "pro"
}
}
If CLAUDE_CONFIG_DIR is set, the file lives under that directory instead. If the file isn't there at all, the build may be using Windows Credential Manager - check cmdkey /list for a Claude/Anthropic entry. The script below prints every location it checks, so you can see exactly where it looked.
Read it yourself: get_bearer_token.py
Save this as get_bearer_token.py and run python get_bearer_token.py. It prints its search path, then your bearer and session tokens, their expiries, your subscription, and scopes. Use --mask to show a safe, shareable version (for a bug report or screen-share), or --bearer to print only the bearer token for piping into curl.
#!/usr/bin/env python3
r"""get_bearer_token.py - print YOUR Claude Code bearer token and session token (Windows / VS Code).
The Claude Code VS Code extension (and the CLI it bundles) sign you in with OAuth and store the
result, on Windows, as PLAINTEXT JSON at:
%USERPROFILE%\.claude\.credentials.json
Shape:
{ "claudeAiOauth": {
"accessToken": "...", <- the BEARER token: sent as `Authorization: Bearer <accessToken>`
"refreshToken": "...", <- the SESSION token: long-lived; mints new access tokens
"expiresAt": 1699999999000, <- accessToken expiry (ms since epoch)
"refreshTokenExpiresAt": 1707777777000,
"scopes": [...], "subscriptionType": "max", "rateLimitTier": "..." } }
- BEARER token = accessToken -> put in the HTTP header to call the API like the extension does.
Short-lived (usually ~hours). If a call 401s, it expired; open Claude Code once
(or run any `claude` command) and it refreshes, then re-run this.
- SESSION token = refreshToken -> the durable credential that represents your logged-in session.
Claude Code uses it to get a fresh accessToken when the old one expires. Treat it
like a password: anyone with it can mint bearers as you.
Usage:
python get_bearer_token.py # print both tokens in full (for your own use)
python get_bearer_token.py --mask # print masked (safe to paste in a bug report / share screen)
python get_bearer_token.py --bearer # print ONLY the bearer token (for piping into curl)
Both tokens are secrets. Do not commit them, screenshot them, or paste them anywhere public.
"""
import argparse, datetime, json, os, sys
def candidate_paths():
r"""Every place Claude Code might keep .credentials.json on Windows, in priority order.
CLAUDE_CONFIG_DIR (if the user set it) wins; otherwise it's %USERPROFILE%\.claude."""
paths = []
cfg = os.environ.get("CLAUDE_CONFIG_DIR")
if cfg:
paths.append((os.path.join(cfg, ".credentials.json"), "$CLAUDE_CONFIG_DIR"))
up = os.environ.get("USERPROFILE") or os.path.expanduser("~")
paths.append((os.path.join(up, ".claude", ".credentials.json"), "%USERPROFILE%\\.claude"))
appdata = os.environ.get("APPDATA")
if appdata:
paths.append((os.path.join(appdata, "Claude", ".credentials.json"), "%APPDATA%\\Claude (fallback)"))
return paths
def find_cred(verbose=True):
"""Print the search, return the first existing credentials path (or None)."""
found = None
if verbose:
print("Searching for .credentials.json:")
for path, label in candidate_paths():
exists = os.path.isfile(path)
if verbose:
print(f" [{'FOUND ' if exists else 'missing'}] {path} ({label})")
if exists and found is None:
found = path
if verbose and found is None:
print(" (none found - on Windows the extension may instead use Credential Manager;")
print(" check `cmdkey /list` for a 'Claude Code'/'anthropic' entry.)")
return found
def _when(ms):
if not ms:
return "?"
dt = datetime.datetime.fromtimestamp(ms / 1000)
delta = dt - datetime.datetime.now()
mins = int(delta.total_seconds() // 60)
rel = f"in {mins} min" if mins >= 0 else f"{-mins} min ago (EXPIRED)"
return f"{dt:%Y-%m-%d %H:%M:%S} ({rel})"
def _mask(tok):
if not tok:
return "(none)"
return f"{tok[:8]}...{tok[-4:]} [len {len(tok)}]"
def main():
ap = argparse.ArgumentParser(description="Print the Claude Code bearer + session tokens (Windows).")
ap.add_argument("--mask", action="store_true", help="mask the token bodies (safe to share)")
ap.add_argument("--bearer", action="store_true", help="print ONLY the bearer token, nothing else")
args = ap.parse_args()
# --bearer is a quiet, pipe-friendly mode: no search chatter, just the token
cred = find_cred(verbose=not args.bearer)
if not cred:
sys.exit("\nNo credentials file found. Sign in first: open the Claude Code extension in "
"VS Code, or run `claude` once, then re-run this.")
with open(cred, encoding="utf-8") as fh:
oauth = (json.load(fh) or {}).get("claudeAiOauth") or {}
bearer = oauth.get("accessToken", "")
session = oauth.get("refreshToken", "")
if args.bearer:
print(bearer)
return
print()
show = _mask if args.mask else (lambda t: t or "(none)")
print("Using: " + cred)
print("-" * 60)
print("BEARER token (accessToken) :", show(bearer))
print(" expires :", _when(oauth.get("expiresAt")))
print("SESSION token (refreshToken) :", show(session))
print(" expires :", _when(oauth.get("refreshTokenExpiresAt")))
print("subscription :", oauth.get("subscriptionType", "?"))
print("scopes :", ", ".join(oauth.get("scopes", []) or []) or "?")
print("-" * 60)
print("Use the BEARER token like the extension does:")
print(' Authorization: Bearer <accessToken>')
print(' anthropic-beta: oauth-2025-04-20')
if __name__ == "__main__":
main()
Example run (masked, so nothing secret is shown):
Searching for .credentials.json:
[FOUND ] C:\Users\you\.claude\.credentials.json (%USERPROFILE%\.claude)
[missing] C:\Users\you\AppData\Roaming\Claude\.credentials.json (%APPDATA%\Claude (fallback))
Using: C:\Users\you\.claude\.credentials.json
------------------------------------------------------------
BEARER token (accessToken) : sk-ant-o...WQAA [len 108]
expires : 2026-08-22 14:28:52 (in 447 min)
SESSION token (refreshToken) : sk-ant-o...egAA [len 108]
expires : 2026-09-20 18:13:27 (in 42431 min)
subscription : pro
scopes : user:inference, user:profile, user:sessions:claude_code, ...
------------------------------------------------------------
Use the BEARER token like the extension does:
Authorization: Bearer <accessToken>
anthropic-beta: oauth-2025-04-20
Proof the bearer works (like the extension)
The extension sends the bearer with an OAuth beta header. You can confirm your own token is live by hitting the account-usage endpoint (this is the same call Claude Code makes to draw your usage bars):
BEARER=$(python get_bearer_token.py --bearer)
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
-H "Authorization: Bearer $BEARER" \
-H "anthropic-beta: oauth-2025-04-20" \
https://api.anthropic.com/api/oauth/usage
# -> HTTP 200 (401 means the bearer expired; open Claude Code once to refresh, then re-run)
That 200 is the whole point: the credential the VS Code extension uses is sitting in a file you can read, it's an OAuth bearer tied to your subscription, and once you have it you can make the same authenticated calls the extension makes.
Security, in one breath
Both tokens are secrets. The bearer expires in hours; the session/refresh token lasts weeks and is password-equivalent - anyone with it can act as you until it expires or you log out. Don't commit them, screenshot them unmasked, or paste them anywhere public. get_bearer_token.py --mask exists precisely so you can share diagnostics without leaking them.
Next
With your bearer token in hand, the follow-up article streams the model's live thinking the way the VS Code extension does - and clears up a myth about the "estimated tokens" counter. Read that one after this.
