ProductDesktopUsage provider APIs

09 — Claude account and window semantics

Documents Claude credential storage, payload fields, profile access, and quota-window semantics.

Summary

Claude credentials live in Keychain by default on macOS, and OAuth profile plus usage endpoints provide identity and bounded quota metadata.

Question and scope

  • Q1 — On macOS, does Claude Code also write/maintain ~/.claude/.credentials.json (or Keychain only)? What is the Keychain generic-password service name, corroborated from multiple public tools/issues? Decides whether a macOS Desktop app can obtain Claude credentials without Keychain access. - Q2 — Public corroboration for payload fields chapter 03 held as single-source: weekly_scoped, seven_day_cowork, seven_day_routines, amber_ladder, seven_day_oauth_apps. Plus: does any public non-banned tool call api.anthropic.com/api/oauth/profile, and what does it return? - Q3 — Window boundary semantics from primary/support sources: is the 5-hour session rolling or fixed-start ("session starts with your first message"?); is the weekly window a fixed reset time? Decides validity of a linear-from-window-start projection model. - Q4 — Any public evidence whether claude setup-token long-lived tokens are accepted by the oauth/usage endpoint (chapter 03 open unknown, line 72)?

This chapter informs jackin-desktop.

Method

Verification cutoff: 2026-07-24 Method: web (WebSearch/WebFetch + GitHub search API via gh, verified live 2026-07-24). Clean-room upheld: no CodexBar/OpenUsage source read; their issue titles cited only as concept evidence. No embedded-instruction/prompt-injection attempts observed in fetched pages (the code.claude.com docs page embeds a benign "fetch llms.txt index" navigation note only).

Findings

Q1 — macOS Keychain vs file

  • Official storage split confirmed live: "On macOS, credentials are stored in the encrypted macOS Keychain. On Linux, credentials are stored in ~/.claude/.credentials.json with file mode 0600. On Windows, credentials are stored in %USERPROFILE%\.claude\.credentials.json" — https://code.claude.com/docs/en/authentication (confidence: HIGH)
  • On macOS a fresh /login writes only to Keychain; ~/.claude/.credentials.json "did not exist by default" — https://github.com/anthropics/claude-code/issues/44028 (confidence: MED-HIGH — detailed user report in the official repo, closed as duplicate without maintainer contradiction)
  • Worse than "not written": macOS Claude Code actively deletes the file. fs trace shows node calling unlink /Users/<user>/.claude/.credentials.json; reported for shared-volume macOS/Linux setups — https://github.com/anthropics/claude-code/issues/1414 (has-repro label; fs-usage evidence) and duplicate lineage https://github.com/anthropics/claude-code/issues/10039 (confidence: MED-HIGH)
  • However, Claude Code on macOS reads ~/.claude/.credentials.json as a fallback if it exists (e.g. over SSH where Keychain is inaccessible): "Claude Code has a file-based credential fallback (~/.claude/.credentials.json) that works over SSH when the file exists" — https://github.com/anthropics/claude-code/issues/44028, corroborated by https://github.com/anthropics/claude-code/issues/29816 ("SSH sessions require re-login despite valid ~/.claude/.credentials.json") (confidence: MED)
  • Keychain generic-password service name is Claude Code-credentials: the official-repo bug #9403 shows security find-generic-password -s "Claude Code-credentials" -w returning the credential JSON ({"claudeAiOauth":{"accessToken":…,"refreshToken":…,"expiresAt":…,"scopes":…}} — same shape as the Linux file, so one parser covers both) — https://github.com/anthropics/claude-code/issues/9403 (confidence: HIGH)
  • Mass independent corroboration of the service name: GitHub code search for "Claude Code-credentials" find-generic-password returns ~3,024 indexed files across independent tools (e.g. matryer/xbar-plugins clawdbar, Z6543/claude-usage, TravisWheelerLab/claude-watch, kristianvalco/claude-usage-bar, torkay/ccpool) — method: gh api search/code, 2026-07-24, CodexBar/OpenUsage excluded from query (confidence: HIGH)
  • Custom CLAUDE_CONFIG_DIR accounts do not use the bare service. Claude Code derives Claude Code-credentials-<suffix>, where suffix is the first eight hexadecimal SHA-256 characters of the absolute config-directory path. jackin already carries this derivation in crates/jackin-instance/src/auth.rs and pins two live entries in auth/tests.rs (~/.claude-chainargos93aecf3d, ~/.claude-work3342f2c7). This in-repo live evidence is authoritative for integration planning; the shared helper must be reused rather than reimplemented. (confidence: HIGH for the two observed accounts and current Claude Code scheme)
  • Third-party Keychain reads can trigger macOS user-consent prompts (ACL requires interaction); an open feature request asks Anthropic to reduce this friction for usage tools via credential export or usage cache — https://github.com/anthropics/claude-code/issues/22144 and https://github.com/anthropics/claude-code/issues/44028 (confidence: MED-HIGH)
  • Security.framework result classification matters: explicit user cancellation (errSecUserCanceled, -128) and authentication/ACL denial (errSecAuthFailed, -25293) are terminal denial for the queried service. errSecInteractionNotAllowed (-25308), common in SSH/headless contexts, proves only that UI cannot be shown; it must be treated as missing/unavailable so Claude Code's documented file fallback remains usable. Item-not-found (-25300) is also missing. (confidence: HIGH for OSStatus semantics; product mapping is the planning decision)
  • Decision answer: on a default macOS install, a file-path-only reader (jackin today: crates/jackin-usage reads only ~/.claude/.credentials.json) gets nothing — the file is absent by default and deleted by macOS Claude Code even if materialized. jackin desktop needs a Security.framework generic-password read for the service derived from the effective Claude config directory. macOS may present consent UI: "Always Allow" makes later reads silent, while "Allow" can prompt again; the app must serialize reads, remember explicit denial per service, and keep the blocking consent path off its main actor. (confidence: HIGH for storage/service/prompt requirement; platform consent-label behavior)

Q2 — public corroboration for payload fields; oauth/profile

All counts are GitHub code-search total_count (approximate, indexed files) on 2026-07-24 with -repo:steipete/CodexBar -repo:steipete/OpenUsage; the jackin repository appears in results and is excluded from independence claims.

  • weekly_scoped — ~840 files; independent decoders/fixtures include tulinmola/claude-bar (Swift), soulduse/ai-token-monitor (Rust), lobehub/lobehub, joshuadavidthomas/vibeusage (Go), akitaonrails/ai-usagebar (Rust) (confidence: MED-HIGH — many independent community observations, still no official doc)
  • seven_day_cowork — ~850 files; e.g. router-for-me/Cli-Proxy-API-Management-Center, kittors/CliRelay (Go), xiaolai/claudepot-app (Rust), onllm-dev/onWatch (Go) (confidence: MED-HIGH)
  • seven_day_routines — ~99 files; e.g. tsouth89/ceiling (rust provider), zioder/TaskbarQuota (C# provider), lfmundim/UsageBar, zhengzizhe/conductor (Swift) (confidence: MED — fewer, and several are spec/notes files rather than live captures; whether this key carries data on routines-active accounts remains open)
  • amber_ladder — ~46 files; e.g. LeenHawk/gproxy (Rust), CrocSwap/claude-meter docs, TopiCsarno/yapcap fixture (confidence: MED)
  • seven_day_oauth_apps — ~1,232 files; e.g. musistudio/claude-code-router, basecamp/omarchy (QML usage plugin), Kuberwastaken/claurst (Rust) (confidence: MED-HIGH)
  • A full public captured oauth/usage response matching jackin live fixture structure exists: five_hour/seven_day with utilization + resets_at, limits[] with kind: session | weekly_all | weekly_scoped (the weekly_scoped entry scoped {model: {display_name: "Fable"}}), seven_day_cowork: null, seven_day_oauth_apps: null, amber_ladder: null, codename keys (tangelo, iguana_necktie, nimbus_quill, cinder_cove, omelette_promotional), spend with amount_minor/currency/exponent, extra_usage, member_dashboard_availablehttps://github.com/TopiCsarno/yapcap/blob/152ea67c3abd44776268627d58533003099da951/fixtures/claude/oauth_usage_response.json (confidence: MED-HIGH — independent capture agreeing field-for-field with jackin in-repo live fixture; chapter 03's "single-source" caveats on weekly_scoped/seven_day_cowork/amber_ladder/seven_day_oauth_apps can be lifted to multi-source)
  • api.anthropic.com/api/oauth/profile exists and is widely used: ~518 indexed files. Reputable non-banned callers: dagger/dagger defines oauthProfileURL = "https://api.anthropic.com/api/oauth/profile" alongside scopes org:create_api_key user:profile user:inferencehttps://github.com/dagger/dagger/blob/main/internal/cmd/dagger/llmconfig/oauth.go; elizaOS/eliza calls it with Authorization: Bearer + anthropic-beta: oauth-2025-04-20 and parses response shape {account: {uuid, email | email_address}, organization: {uuid}}https://github.com/elizaOS/eliza/blob/main/packages/auth/src/oauth-flow.ts (confidence: MED-HIGH for existence + account-email/uuid + organization-uuid; LOW/unknown for a plan label field)
  • Official-repo corroboration of the endpoint plus its scope gate: GET /api/oauth/profile returns 403 "OAuth token does not meet scope requirement any_of(user:profile, user:office)" for inference-only tokens; the same issue also names GET /api/oauth/claude_cli/roles (entitlements/roles) — https://github.com/anthropics/claude-code/issues/79360 (confidence: HIGH for endpoint existence and scope requirement)
  • The profile endpoint requires user:profile or user:office scope and returns account email, enabling jackin email attribution without traffic sniffing.

Q3 — window boundary semantics

  • The 5-hour session is a bounded window with a countdown, per live support docs: "Current session: How much of your plan's five-hour session limit you've used thus far, plus the amount of time remaining in the session" — https://support.claude.com/en/articles/9797557-usage-limit-best-practices (confidence: HIGH)
  • Session limit-reached messaging carries an explicit reset time: "5-hour limit reached - resets [time]" and "5-hour limit resets [time] - continuing with usage credits" — https://support.claude.com/en/articles/12466728-troubleshoot-claude-error-messages (confidence: HIGH)
  • "Session starts with your first message" (fixed-start, not rolling): this exact wording lived in the retired support article "Does Claude have any message limits?" (8602283) — the URL now 404s after the support.anthropic.com → support.claude.com migration, and no current live article restates the trigger; secondary echoes (e.g. https://usagebar.com/blog/when-does-claude-code-usage-reset, https://pranoti.thesciencetalk.com/perspectives/claude-5-hour-session-limit-how-it-works/) all quote the first-message-start model (confidence: MED — historical primary wording, currently only secondary-corroborated; no source anywhere describes a continuously-rolling 5-hour lookback)
  • Weekly window is fixed-reset, account-assigned, stated live and unambiguously: "Weekly limits reset at a fixed time each week that is assigned to your account. Your reset day and time stay the same regardless of when you start using Claude or when your subscription begins, and you receive your full weekly allowance each cycle." — https://support.claude.com/en/articles/11049741-what-is-the-max-plan (confidence: HIGH)
  • Projection-model verdict: linear-from-window-start is structurally valid for both windows — each is a fixed interval with a known end (resets_at is provided per window in the oauth/usage payload itself, see yapcap fixture above), not a rolling lookback. For the session window the start is user-triggered (first message), so anchor projections on the server-supplied resets_at (start = resets_at - 5h) rather than on local first-request observations. Weekly resets_at gives the fixed weekly boundary directly. (confidence: HIGH for using resets_at as the anchor; MED for the session start=first-message assumption feeding any "elapsed fraction" denominator)

Q4 — setup-token vs oauth/usage

  • Rejected. A detailed official-repo issue shows a claude setup-token credential (Max plan) failing the usage endpoint with a scope error: "GET /api/oauth/usage returns 403: 'OAuth token does not meet scope requirement user:profile'". Root cause quoted: "Setup-token tokens carry only the user:inference scope, and the entitlement endpoints reject that scope" — while POST /v1/messages with the same token succeeds — https://github.com/anthropics/claude-code/issues/79360 (confidence: MED-HIGH — precise first-hand report with quoted response bodies in anthropics/claude-code; no maintainer contradiction; consistent with the official doc's "It can only make model requests" — https://code.claude.com/docs/en/authentication)
  • Scope contrast corroborated independently: interactive /login-style flows request org:create_api_key user:profile user:inference (dagger's OAuth constants — https://github.com/dagger/dagger/blob/main/internal/cmd/dagger/llmconfig/oauth.go), and user:profile is exactly the scope oauth/usage demands per #79360 (confidence: MED-HIGH)
  • Concept-level corroboration from tool ecosystems (issue titles only, clean-room): CodexBar issue #1894 proposes a Claude usage source "reading anthropic-ratelimit-unified-* headers (multi-account with setup-tokens / user:inference)" — i.e. header-scraping precisely because setup-tokens cannot use the usage endpoint — https://github.com/steipete/CodexBar/issues/1894; similarly Nanako0129/TokenBar #27 "Claude usage limits for setup-token users via ratelimit headers" — https://github.com/Nanako0129/TokenBar/issues/27 (confidence: MED, concept evidence)
  • Chapter 03 line 72 open unknown → closed: CLAUDE_CODE_OAUTH_TOKEN setup-tokens are not a valid credential for jackin Claude usage polling; only /login-minted OAuth credentials (file or Keychain) carry user:profile.

Dead ends and contradictions

  • Issue #44028 states the Keychain item is "service 'Claude Safe Storage' with account 'Claude Key'" — contradicts #9403 and ~3k public tools using Claude Code-credentials. "Claude Safe Storage" is the Claude Desktop (Electron safeStorage) key name; the reporter almost certainly conflated the two products. Treat Claude Code-credentials as canonical for the CLI — https://github.com/anthropics/claude-code/issues/44028 vs https://github.com/anthropics/claude-code/issues/9403
  • #9403 documents a transient v2.0.14 bug where the CLI wrote to Claude Code-credentials but read from service Claude Code — a version-specific read/write mismatch, not a second canonical name — https://github.com/anthropics/claude-code/issues/9403
  • Weekly-reset contradiction from chapter 03 stands unresolved: support says fixed weekly reset (11049741), while an 11-day longitudinal poll observed seven_day.utilization refreshing every ~72h — https://gist.github.com/monperrus/3ac4b303a84946bbeaf2b1123ee99491. Continue treating resets_at as window-tail metadata, not a refill promise.
  • Current support.claude.com articles do not document a session-start reset trigger. "How do usage and length limits work?" (11647753) contains no session/reset statement, while "Models, usage, and limits in Claude Code" (14552983) says only that Enterprise seats meter on a "rolling window" without specifying its duration.
  • opgginc/opencode-bar docs/AI_USAGE_API_REFERENCE.md contains no profile-endpoint section (checked via API) — dead end for profile-shape corroboration; eliza + dagger + #79360 used instead.

Implications for jackin

Desktop needs the shared Security.framework credential reader and must classify denial, unavailable interaction, and missing items without bypassing Claude’s file fallback.

Limitations and unknowns

  • Whether api/oauth/profile returns a plan/subscription label (eliza parses only account.uuid, account.email/email_address, organization.uuid); plan attribution may still need the credential file's subscriptionType or the claude_cli/roles endpoint — needs an operator-authenticated capture.
  • Which key carries routines data on a routines-active account (seven_day_routines vs seven_day_cowork); all public captures observed show seven_day_cowork: null.
  • The undocumented mechanism by which macOS Claude Code sometimes writes ~/.claude/.credentials.json after a manual Keychain unlock (#44028 observed it "spontaneously"); do not design around it.
  • A current live primary source for "session starts with your first message" (MED via retired-article echoes only).
  • Whether Anthropic will ship the #22144 credential-export/usage-cache path, which would remove the Keychain-prompt requirement for jackin desktop.

Sources

On this page