diff --git a/ClaudeChecker/Info.plist b/ClaudeChecker/Info.plist
index d30e1dd..486b9e4 100644
--- a/ClaudeChecker/Info.plist
+++ b/ClaudeChecker/Info.plist
@@ -15,9 +15,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.2.1-beta.5
+ 1.2.1-beta.6
CFBundleVersion
- 53
+ 54
LSMinimumSystemVersion
13.0
LSUIElement
diff --git a/ClaudeChecker/UsageViewModel.swift b/ClaudeChecker/UsageViewModel.swift
index e9e9e05..24f5f28 100644
--- a/ClaudeChecker/UsageViewModel.swift
+++ b/ClaudeChecker/UsageViewModel.swift
@@ -128,12 +128,25 @@ class UsageViewModel: ObservableObject {
// MARK: - Bootstrap (org ID + email + plan label in one call)
+ private func claudeAPIRequest(for url: URL) async -> URLRequest {
+ var req = URLRequest(url: url)
+ req.setValue("application/json, text/plain, */*", forHTTPHeaderField: "accept")
+ req.setValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent")
+ req.setValue("https://claude.ai", forHTTPHeaderField: "Origin")
+ req.setValue("https://claude.ai/", forHTTPHeaderField: "Referer")
+ req.setValue("same-origin", forHTTPHeaderField: "sec-fetch-site")
+ req.setValue("cors", forHTTPHeaderField: "sec-fetch-mode")
+ req.setValue("empty", forHTTPHeaderField: "sec-fetch-dest")
+ if let cookie = await claudeCookieHeader() {
+ req.setValue(cookie, forHTTPHeaderField: "Cookie")
+ }
+ return req
+ }
+
private func fetchBootstrap() async throws -> (orgId: String?, email: String?, planLabel: String?) {
let url = URL(string: "https://claude.ai/api/bootstrap")!
- var req = URLRequest(url: url)
- req.setValue("application/json", forHTTPHeaderField: "accept")
- guard let cookie = await claudeCookieHeader() else { throw AppError.notAuthenticated }
- req.setValue(cookie, forHTTPHeaderField: "Cookie")
+ var req = await claudeAPIRequest(for: url)
+ guard req.value(forHTTPHeaderField: "Cookie") != nil else { throw AppError.notAuthenticated }
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse else { throw AppError.networkError }
if http.statusCode == 401 || http.statusCode == 403 { throw AppError.notAuthenticated }
@@ -152,11 +165,9 @@ class UsageViewModel: ObservableObject {
if orgId == nil {
orgId = (json["organizations"] as? [[String: Any]])?.first?["uuid"] as? String
}
- if orgId == nil, let cookie = await claudeCookieHeader() {
+ if orgId == nil {
// Final fallback: fetch /api/organizations directly
- var orgsReq = URLRequest(url: URL(string: "https://claude.ai/api/organizations")!)
- orgsReq.setValue("application/json", forHTTPHeaderField: "accept")
- orgsReq.setValue(cookie, forHTTPHeaderField: "Cookie")
+ let orgsReq = await claudeAPIRequest(for: URL(string: "https://claude.ai/api/organizations")!)
if let (orgsData, orgsResp) = try? await URLSession.shared.data(for: orgsReq),
let orgsHttp = orgsResp as? HTTPURLResponse, orgsHttp.statusCode == 200,
let orgs = try? JSONSerialization.jsonObject(with: orgsData) as? [[String: Any]] {
@@ -187,9 +198,7 @@ class UsageViewModel: ObservableObject {
private func fetchUsage(orgId: String) async throws -> UsageResponse {
let url = URL(string: "https://claude.ai/api/organizations/\(orgId)/usage")!
- var req = URLRequest(url: url)
- req.setValue("application/json", forHTTPHeaderField: "accept")
- if let cookie = await claudeCookieHeader() { req.setValue(cookie, forHTTPHeaderField: "Cookie") }
+ let req = await claudeAPIRequest(for: url)
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse else { throw AppError.networkError }
if http.statusCode == 401 || http.statusCode == 403 { throw AppError.notAuthenticated }
@@ -199,9 +208,7 @@ class UsageViewModel: ObservableObject {
private func fetchPrepaidCredits(orgId: String) async throws -> PrepaidCredits? {
let url = URL(string: "https://claude.ai/api/organizations/\(orgId)/prepaid/credits")!
- var req = URLRequest(url: url)
- req.setValue("application/json", forHTTPHeaderField: "accept")
- if let cookie = await claudeCookieHeader() { req.setValue(cookie, forHTTPHeaderField: "Cookie") }
+ let req = await claudeAPIRequest(for: url)
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return nil }
return try? JSONDecoder().decode(PrepaidCredits.self, from: data)
@@ -209,9 +216,7 @@ class UsageViewModel: ObservableObject {
private func fetchOverageSpendLimit(orgId: String) async throws -> OverageSpendLimit? {
let url = URL(string: "https://claude.ai/api/organizations/\(orgId)/overage_spend_limit")!
- var req = URLRequest(url: url)
- req.setValue("application/json", forHTTPHeaderField: "accept")
- if let cookie = await claudeCookieHeader() { req.setValue(cookie, forHTTPHeaderField: "Cookie") }
+ let req = await claudeAPIRequest(for: url)
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return nil }
return try? JSONDecoder().decode(OverageSpendLimit.self, from: data)
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 2ded05e..c408a82 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -6,3 +6,4 @@
- Fixed login window auto-closing before the user could sign in — the login window now correctly loads the `/login` page so it only detects auth after the actual sign-in redirect
- Fixed "No API key configured" showing after signing out — now correctly shows "Not signed in" with a prompt to sign in
- Added `/api/organizations` as a final fallback for org ID resolution when the bootstrap API response doesn't include it
+- Fixed usage data not loading after sign-in — API requests now include required browser-like headers (Origin, Referer, User-Agent) that Claude's usage endpoints require