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

@imlunahey/psn-api

v1.0.0

Published

PlayStation Network API client

Downloads

145

Readme

@imlunahey/psn-api

A modern PlayStation Network API client. One stateful client, automatic token refresh, typed errors, ergonomic resource handles.

import { PSN } from '@imlunahey/psn-api';

const psn = new PSN({ npsso: process.env.NPSSO! });
const luna = await psn.users.byName('imlunahey');

const titles = await luna.trophies.titles();
const recent = titles[0];
const trophies = await recent.earned(); // definitions + earned status merged

Installation

pnpm add @imlunahey/psn-api
# or npm / yarn / bun

Requires Node 20+. Ships ESM and CJS.

Getting your NPSSO

The NPSSO is a long-lived session cookie from sony.com. The library exchanges it for OAuth tokens lazily on the first request and refreshes them automatically thereafter.

  1. In a browser, sign in to https://www.playstation.com.
  2. Visit https://ca.account.sony.com/api/v1/ssocookie.
  3. Copy the npsso value from the JSON response — it looks like {"npsso":"abc…xyz"}.
  4. Pass it as the NPSSO env var (or directly to new PSN({ npsso })).

NPSSO tokens are good for about 60 days. The library only uses them once — for the initial OAuth exchange — and rotates the resulting access/refresh tokens on its own.

Quick start

import { PSN } from '@imlunahey/psn-api';

const psn = new PSN({ npsso: process.env.NPSSO! });

// the authenticated user
const me = await psn.users.me();
console.log(await me.profile());

// any user by online ID
const luna = await psn.users.byName('imlunahey');
const presence = await luna.presence();

// known account ID — no network call to construct the handle
const friend = psn.users.byAccountId('1234567890');

// list trophy titles (one page, or async-iterate everything)
const titles = await luna.trophies.titles({ limit: 50 });
for await (const t of luna.trophies.titles.all()) {
  console.log(t.name);
}

// trophies for one title — definitions + earned status merged
const recent = titles[0]!;
const trophies = await recent.earned();
console.log(`${trophies.filter((t) => t.earned).length}/${trophies.length} earned`);

What you can do

Resources, mostly grouped onto psn.users, with handle methods for each user.

// User lookups
psn.users.me()                        // -> MeHandle (extends UserHandle)
psn.users.byName(onlineId)            // -> UserHandle
psn.users.byAccountId(accountId)      // -> UserHandle (no network call)

// Search
psn.search.users('luna')              // -> SearchUserResult[]
psn.search.userHandle(result)         // -> UserHandle
psn.search.universal(q, ['…'])        // -> raw multi-domain response

// On any UserHandle
user.profile()
user.presence()
user.shareLink()                      // public share URL + image asset
user.friends()                        // Listable<UserHandle>
user.playedGames()                    // Listable<PlayedGame>

// Trophy operations live under .trophies
user.trophies.stats()                 // account-wide level/tier/counts
user.trophies.titles()                // Listable<TrophyTitleHandle>
user.trophies.forTitles(ids)          // bulk lookup by Sony title ID, max 5

// On MeHandle (everything above, plus):
me.devices()                          // logged-in consoles/handhelds
me.recentlyPlayed()                   // PS4/PS5, GraphQL
me.purchased()                        // PS4/PS5 purchase library, GraphQL
me.friendRequests()                   // received friend requests
me.blocked()                          // Listable<UserHandle>

// On a TrophyTitleHandle
title.groups()                        // groups + per-group earned/defined/progress
title.earned(groupId?)                // trophies with earned status merged
title.summary()                       // total earned/defined + progress

Every list endpoint (trophies.titles, friends, playedGames, friendRequests, blocked, etc.) is callable directly for a single page or via .all() for an async iterator over every page.

Persisting tokens

Construction is synchronous; the NPSSO → OAuth exchange happens on the first request. Subscribe to token changes to keep them across runs:

import { readFileSync } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { PSN, type Tokens } from '@imlunahey/psn-api';

const TOKEN_FILE = '.psn-tokens.json';

const loadTokens = (): Tokens | null => {
  try {
    return JSON.parse(readFileSync(TOKEN_FILE, 'utf8')) as Tokens;
  } catch {
    return null;
  }
};

const cached = loadTokens();
const psn = cached
  ? new PSN({ tokens: cached })
  : new PSN({ npsso: process.env.NPSSO! });

psn.onTokenRefresh((tokens) =>
  writeFile(TOKEN_FILE, JSON.stringify(tokens, null, 2)),
);

onTokenRefresh fires on the initial auth and on every subsequent refresh. It returns an unsubscribe function.

Error handling

Every error extends PSN.Error. Branch on the typed subclasses:

import { PSN } from '@imlunahey/psn-api';

try {
  await psn.users.byName('does-not-exist');
} catch (e) {
  if (e instanceof PSN.NotFound) {
    // unknown online ID, deleted account, etc.
  } else if (e instanceof PSN.RateLimited) {
    console.log('retry after', e.retryAfter, 'seconds');
  } else if (e instanceof PSN.AuthExpired) {
    // refresh token rejected — re-auth with a fresh NPSSO
  } else if (e instanceof PSN.AuthInvalid) {
    // bad NPSSO or rejected credentials
  } else if (e instanceof PSN.Error) {
    // any other PSN error
  }
}

Examples

Runnable scripts in examples/:

| Script | What it does | | --- | --- | | examples/basic.ts | Authenticate, fetch own profile, look up another user | | examples/persist-tokens.ts | Cache tokens to disk and reuse across runs | | examples/trophies.ts | Fetch a title's trophies with earned status | | examples/iterate-all.ts | Async-iterate every trophy title | | examples/search.ts | Search for users by name and promote a result to a handle | | examples/friends.ts | List friends and fetch their profiles concurrently | | examples/recently-played.ts | PS4/PS5 recently-played games (GraphQL) | | examples/trophy-profile.ts | Account-wide trophy level, tier, and counts |

NPSSO=... pnpm example:basic

Credits

Built as an alternative to psn-api by the Achievements App team. Their library reverse-engineered the PSN endpoints, auth flow, and the GraphQL persisted-query hashes used here — none of this would exist without that mapping work. Huge thanks to that project.

This rewrite covers the same surface but trades the function-based shape (with manual auth threading on every call) for a single stateful client, automatic token refresh, async-iterator pagination, typed errors, and resource handles for users, trophy titles, and search.

License

MIT