Download GeForce Now Playtime
GeForce Now shows your session history on the account page, but there is no export button. I wanted my playtime data as JSON so I can analyse it later. So I built a small CLI tool that fetches the session history from NVIDIA's API and appends it to a local file.
The tricky part
The API at api-prod.nvidia.com requires an idtoken header -- a JWT issued by login.nvidia.com.
The token expires after one hour and there is no public way to get one programmatically.
No OAuth client credentials, no API key, nothing.
The solution is Playwright with a persistent browser context. The script opens the GFN account page, waits for the page to make an API call, and intercepts the token from the request headers:
def get_token_via_browser() -> str: from playwright.sync_api import sync_playwright captured_token = None def on_request(request): nonlocal captured_token if "api-prod.nvidia.com" in request.url and not captured_token: token = request.headers.get("idtoken") if token: captured_token = token BROWSER_DATA_DIR.mkdir(exist_ok=True) with sync_playwright() as p: context = p.chromium.launch_persistent_context( str(BROWSER_DATA_DIR), headless=False, ) page = context.pages[0] if context.pages else context.new_page() page.on("request", on_request) page.goto(GFN_ACCOUNT_URL, wait_until="domcontentloaded") page.wait_for_event( "request", predicate=lambda r: ( "api-prod.nvidia.com" in r.url and r.headers.get("idtoken") ), timeout=120_000, ) context.close() return captured_token
Because the context is persisted in .browser-data/, you only need to log in once.
On subsequent runs Playwright reuses the session cookies and the token gets captured without interaction.
The API
The session history endpoint is /gfn-paywall-api/api/v2/userplaytime/sessionshistory.
It takes spanStartDate and spanEndDate as query parameters and returns something like this:
{ "sessionHistory": [ { "sessionStartDate": "2026-04-11T17:58:01.000Z", "sessionEndDate": "2026-04-11T20:59:52.000Z", "gameId": "100358911", "gameTitle": "Baldur's Gate 3", "totalPlaytime": 181.0 } ] }
One catch: the API only returns data within a single billing period. If your date range spans multiple months you get incomplete results. The fix is to chunk requests by calendar month:
chunk_start = start.replace(day=1) while chunk_start < end: if chunk_start.month == 12: chunk_end = chunk_start.replace(year=chunk_start.year + 1, month=1) else: chunk_end = chunk_start.replace(month=chunk_start.month + 1) params["spanStartDate"] = chunk_start.strftime(...) params["spanEndDate"] = min(chunk_end, end).strftime(...) # fetch and collect chunk_start = chunk_end
Sessions at month boundaries can appear in both adjacent chunks, so the results need to be deduplicated.
Usage
Example output for me:
$ uv run nvidia-playtime Opening GFN account page... If prompted, log in with your NVIDIA account. Token captured. Fetching sessions: 2026-03-13 to 2026-04-12 2026-03-01 .. 2026-04-01 2026-04-01 .. 2026-05-01 Added 28 new sessions, 28 total in sessions.json Date Game Duration ---------------------------------------------------------------------- 2026-03-14 18:53:35 Baldur's Gate 3 3h 11m 2026-03-14 23:17:01 Baldur's Gate 3 1h 34m ... 2026-04-11 17:58:01 Baldur's Gate 3 3h 01m ---------------------------------------------------------------------- Total 48h 12m 28 sessions
Running it again only appends new sessions.
What I learned
NVIDIA has a second endpoint at /userplaytime/history that takes a memberSince parameter.
It sounds like it should return the full history, but it only goes back about 6 weeks and doesn't include game titles.
Not useful.
Using Playwright to intercept tokens from a real browser session is a nice pattern for APIs that don't offer proper auth for scripts. This is especially relevant because of the second factor, which is an email with a link you need to open. The persistent context makes it almost seamless after the first login.