npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

oura-bun

v0.1.1

Published

Unofficial Oura client for Bun: browser login, web API reads and durable OAuth rotation

Readme

oura-bun

Unofficial Oura client and CLI for Bun. Initial browser authorization, durable single-use refresh-token rotation, paginated API reads, and a separate client for the Oura website's private endpoints.

Not affiliated with Oura. Bring your own Oura account and developer application. No hosted proxy, telemetry, bundled credentials, or stored passwords.

Install with Bun

bun add [email protected]
bunx --no-install oura-bun --help

Or clone and run:

git clone https://github.com/jcpsimmons/oura-bun.git
cd oura-bun
bun install --frozen-lockfile
bun src/cli.ts

Requires Bun 1.3 or newer. This is a Bun-native TypeScript package, not a Node.js CLI. GitHub installation also works: bun add github:jcpsimmons/oura-bun.

OAuth: recurring jobs

  1. Register an application at Oura Developer. Add http://localhost:8765/callback to its redirect URIs.
  2. Set OURA_CLIENT_ID and OURA_CLIENT_SECRET in your environment or a local ignored .env. Never put credentials in command arguments or source control.
  3. Run bunx --no-install oura-bun login. Complete Oura's login and consent in your browser. Use --no-open to open the printed local URL yourself.
  4. Read records. Refresh happens automatically when needed.
bunx --no-install oura-bun status
bunx --no-install oura-bun get daily_sleep --start 2026-01-01 --end 2026-01-31

get writes private data as NDJSON to stdout. Redirect it to a private location, not a public CI log. Login and status never print tokens.

Defaults request daily workout spo2 stress heart_health. For the sleep example, login --scopes daily is sufficient. Choose only the scopes you need; additional scopes require consent again. The defaults do not include personal, email, heartrate, tag or session. --redirect must match your registered loopback callback. --state /absolute/private/file selects the credential file (this option is a filesystem path, not OAuth's anti-CSRF state).

Save this as sleep-count.ts after completing CLI login, then run bun sleep-count.ts. It uses the same credential file as the CLI and prints a record count without exposing sleep data.

import { OAuth, OuraClient, FileStore, type Tokens } from 'oura-bun';
import { join } from 'node:path';
import { homedir } from 'node:os';

const auth = new OAuth({
  clientId: process.env.OURA_CLIENT_ID!,
  clientSecret: process.env.OURA_CLIENT_SECRET!,
  tokenUrl: process.env.OURA_TOKEN_URL,
  store: new FileStore<Tokens>(join(homedir(), '.config/oura-bun/oauth.tokens.json')),
});
const client = new OuraClient(auth);
let count = 0;
for await (const _row of client.records('daily_sleep', {
  start_date: '2026-01-01',
  end_date: '2026-01-31',
})) {
  count += 1; // Replace with your own private storage.
}
console.log(`Read ${count} daily sleep records.`);

status inspects the local credential file; it does not check whether Oura still accepts the credentials. A successful get confirms API access. See setup, API usage and troubleshooting.

Refresh guarantees and limits

  • File stores use exclusive cross-process locks, atomic rename, fsync and mode 0600. The containing directory is created with mode 0700. Existing parent directory permissions are not modified.
  • Each client rereads the file under the lock. A stale 401 response cannot consume the replacement token twice.
  • A durable refreshPending marker is written before sending the single-use refresh token. A network timeout or crash with an ambiguous outcome requires login again, rather than silently replaying an invalid token.
  • Rotated credentials must be persisted before access is returned. Upstream error bodies and tokens are not included in errors.
  • File locks coordinate processes sharing a local filesystem. They do not coordinate separate machines or independent containers. Implement Store.exclusive using a distributed lock for those environments, and read the latest durable state inside it. Never copy one rotating token into several independent stores.
  • A crash can leave a lock directory. Confirm the owning process has stopped before removing it. Pending refresh state still requires reauthorization.
  • Files are private but not encrypted at rest by this library; use OS disk encryption or a secret-manager Store for stronger protection.

Current Oura authorization redirects to moi.ouraring.com/oauth/v2/ext/oauth-authorize. This package defaults token exchanges to the corresponding verified /oauth/v2/ext/oauth-token. Older Oura docs still list api.ouraring.com/oauth/token. Legacy clients can explicitly set OURA_TOKEN_URL to that endpoint. There is no automatic fallback or token replay across issuers.

Website API: experimental

The web adapter was derived from Oura's public JavaScript, including its SSO cookie exchange and daily-data requests. It uses an isolated browser context for sign-in; it does not scrape your normal browser's cookies or ask for your password.

# Use installed Chrome, or install Playwright Chromium with: bunx playwright install chromium
bunx --no-install oura-bun web-login --channel chrome
bunx --no-install oura-bun web-daily --start 2026-01-01 --end 2026-01-31
import { OuraWeb } from 'oura-bun/web';
const web = new OuraWeb('/absolute/private/oura.session.json');
await web.login({ channel: 'chrome' }); // user completes Oura login/MFA
const scores = await web.scores('2026-01-01');
const days = await web.daily('2026-01-01', '2026-01-31');

Cookies returned by each request are saved before returning data. An expired session produces AuthRequired; rerun web-login to renew through Oura's own SSO. The browser may reuse still-valid SSO cookies from this package's saved session. The website does not expose a browser refresh-token endpoint in its inspected client, so we do not invent one.

Oura says Oura on the Web will be discontinued later in 2026. Use OAuth for durable integrations. Private endpoint contracts may change. The web adapter intentionally exposes only three inspected read endpoints; it does not provide account deletion, consent changes, or organization writes.

See protocol evidence for endpoint provenance, verification and limitations.

Develop

bun install --frozen-lockfile
bun test
bun run typecheck

Tests use synthetic credentials and temporary private stores. They cover refresh concurrency, durability failures, invalid grants, callback state, pagination, retry limits and web cookie renewal. CI needs no credentials.