Table of contents
Prerequisites
First and foremost, here’s the docs:
Now that we have that out of the way, there are two things that you need:
- A SteamId
- A Steam API Key
See below for instruction for each of these
How do I find my SteamId?
Login to Steam on the web or in the desktop app.
Go to the upper-right corner and click on
[Your Username] -> Account Details
In the header, under “[Username]‘s Account”, you’ll find your 17 Digit SteamId that looks like this:
SteamdID: XXXXXXXXXXXXXXXXX
How do I find my Steam API Key?
you can visit the Steam API Key Dashboard to register a new API key.
Here’s the full URL:
https://steamcommunity.com/dev/apikey
Steam API Reference
https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0002.29
GetPlayerSummary - http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=XXXXXXXXXXXXXXXXXXXXXXX&steamids=76561197960435530
GetOwnedGames - http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=XXXXXXXXXXXXXXXXX&steamid=76561197960434622&format=json
GetPlayerAchievements - http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?appid=440&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&steamid=76561197972495328
GetUserStatsForGame - http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=440&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&steamid=76561197972495328
export const STEAM_ID = import.meta.env.STEAM_ID ?? process.env.STEAM_ID;
export const STEAM_IDS = import.meta.env.STEAM_IDS ?? process.env.STEAM_IDS;
export const STEAM_API_KEY =
import.meta.env.STEAM_API_KEY ?? process.env.STEAM_API_KEY;
const BASE_URL = "https://api.steampowered.com";
const BASE_PATHS = {
GetPlayerSummaries: "/ISteamUser/GetPlayerSummaries/v0002",
GetOwnedGames: "/IPlayerService/GetOwnedGames/v0001",
GetFriendList: "/ISteamUser/GetFriendList/v0001",
GetNewsForApp: "/ISteamNews/GetNewsForApp/v0002",
GetPlayerAchievements: "/ISteamUserStats/GetPlayerAchievements/v0001",
GetUserStatsForGame: "/ISteamUserStats/GetUserStatsForGame/v0002",
};
export interface SteamApiResponse<T> {
response: T;
}
export type GetPlayerSummariesResponse = SteamApiResponse<{
players: Array<{
steamid: string;
communityvisibilitystate: number;
profilestate: number;
personaname: string;
profileurl: string;
avatar: string;
avatarmedium: string;
avatarfull: string;
avatarhash: string;
lastlogoff: number;
personastate: number;
realname: string;
primaryclanid: string;
timecreated: number;
personastateflags: number;
gameextrainfo: string;
gameid: string;
}>;
}>;
export type GetOwnedGamesResponse = SteamApiResponse<{
games_count: number;
games: Array<
{
appid: number;
playtime_forever: number;
playtime_windows_forever: number;
playtime_mac_forever: number;
playtime_linux_forever: number;
playtime_deck_forever: number;
rtime_last_played: number;
playtime_disconnected: number;
// when include_appinfo is true
name: string;
has_community_visibile_stats: true;
content_descriptorids: Array<number>;
img_icon_url: string;
} & (
| { enhanced?: false }
| {
enhanced: true;
achievements: GetPlayerAchievementsResponse["playerstats"]["achievements"];
achievementsCountTotal: number;
achievementsCountAchieved: number;
achievementsCountPercent: string;
stats: GetUserStatsResponse["playerstats"]["stats"];
statsCount: number;
news: GetNewsForAppResponse["appnews"]["newsitems"];
newsCount: number;
}
)
>;
}>;
export type GetPlayerAchievementsResponse = {
playerstats: {
success: boolean;
steamID: string;
gameName: string;
achievements: Array<{
apiname: string;
achieved: number; // 0 or 1
unlocktime: number;
}>;
};
};
export type GetUserStatsResponse = {
playerstats: {
success: boolean;
steamID: string;
gameName: string;
stats: Array<{
name: string;
value: number;
}>;
};
};
export type GetFriendsListResponse = Array<{
steamid: string;
relationiship: string;
friends_since: number;
}>;
export type GetNewsForAppResponse = {
appnews: {
appid: number;
count: number;
newsitems: Array<{
gid: string;
appid: number;
title: string;
url: string;
is_external_url: boolean;
author: string;
contents: string;
feedlabel: string;
date: number;
feed_type: number;
}>;
};
};
interface SteamAPI {
GetPlayerSummaries: (
steamIds: Array<string>,
) => Promise<GetPlayerSummariesResponse>;
GetPlayerAchievements: (
steamId: string,
appId: number,
) => Promise<GetPlayerAchievementsResponse>;
GetUserStatsForGame: (
steamId: string,
appId: number,
) => Promise<GetUserStatsResponse>;
GetOwnedGames: (steamId: string) => Promise<GetOwnedGamesResponse>;
GetFriendList: (
steamId: string,
relationship?: "all" | "friend",
) => Promise<GetFriendsListResponse>;
GetNewsForApp: (
appId: number,
count?: number,
maxlength?: number,
) => Promise<GetNewsForAppResponse>;
}
export function makeSteamAPI(
baseUrl: string = BASE_URL,
apiKey: string = STEAM_API_KEY,
): SteamAPI {
let authUrl: URL = new URL(baseUrl);
authUrl.searchParams.set("key", apiKey);
const self: SteamAPI = {
GetPlayerSummaries: async (steamIds) => {
const url = new URL(authUrl);
url.pathname = BASE_PATHS.GetPlayerSummaries;
url.searchParams.set("steamids", steamIds.join(","));
url.searchParams.set("format", "json");
return handleFetch<GetPlayerSummariesResponse>(url.href).catch(
(reason) => {
console.error(
"[Steam] :: Failed to fetch player summaries ::",
reason,
);
return { response: { players: [] } };
},
);
},
GetOwnedGames: async (steamId) => {
const url = new URL(authUrl);
url.pathname = BASE_PATHS.GetOwnedGames;
url.searchParams.set("steamid", steamId);
url.searchParams.set("include_appinfo", "true");
url.searchParams.set("include_played_free_games", "true");
url.searchParams.set("format", "json");
return handleFetch<GetOwnedGamesResponse>(url.href).catch((reason) => {
console.error("[Steam] :: Failed to fetch owned games ::", reason);
return { response: { games: [], games_count: 0 } };
});
},
GetNewsForApp: async (appId, count = 5, maxlength = 1000) => {
const url = new URL(authUrl);
url.pathname = BASE_PATHS.GetNewsForApp;
url.searchParams.set("appid", appId.toString());
url.searchParams.set("count", count.toString());
url.searchParams.set("maxlength", maxlength.toString());
url.searchParams.set("format", "json");
return handleFetch<GetNewsForAppResponse>(url.href).catch((reason) => {
console.error("[Steam] :: Failed to fetch news ::", reason);
return { appnews: { count: 0, newsitems: [], appid: appId } };
});
},
GetFriendList: async (steamId, relationship = "friend") => {
const url = new URL(authUrl);
url.pathname = BASE_PATHS.GetFriendList;
url.searchParams.set("steamid", steamId);
url.searchParams.set("relationship", relationship);
url.searchParams.set("format", "json");
return handleFetch<GetFriendsListResponse>(url.href).catch((reason) => {
console.error("[Steam] :: Failed to fetch friend list ::", reason);
return [];
});
},
GetPlayerAchievements: async (steamId, appId) => {
const url = new URL(authUrl);
url.pathname = BASE_PATHS.GetPlayerAchievements;
url.searchParams.set("appid", appId.toString());
url.searchParams.set("steamid", steamId);
return handleFetch<GetPlayerAchievementsResponse>(url.href).catch(
(reason) => {
console.error(
"[Steam] :: Failed to fetch player achievements ::",
reason,
);
return {
playerstats: {
achievements: [],
gameName: "N/A",
steamID: "N/A",
success: false,
},
};
},
);
},
GetUserStatsForGame: async (steamId, appId) => {
const url = new URL(authUrl);
url.pathname = BASE_PATHS.GetUserStatsForGame;
url.searchParams.set("appId", appId.toString());
url.searchParams.set("steamid", steamId);
return handleFetch<GetUserStatsResponse>(url.href).catch((reason) => {
console.error("[Steam] :: Failed to fetch player stats ::", reason);
return {
playerstats: {
gameName: "N/A",
stats: [],
success: false,
steamID: "N/A",
},
};
});
},
};
return self;
}
async function enhanceGame(
steamId: string,
game: GetOwnedGamesResponse["response"]["games"][number],
srv: { steam: SteamAPI },
) {
const { steam } = srv;
}
export async function getProfilesFromSteamIds(
steamIds: Array<string>,
srv: { steam: SteamAPI },
) {
const { steam } = srv;
const playerSummariesResponse = await steam.GetPlayerSummaries(steamIds),
playerSummaries = playerSummariesResponse.response.players;
const getProfileGames = async (
profile: GetPlayerSummariesResponse["response"]["players"][number],
) => {
const ownedGamesResponse = await steam.GetOwnedGames(
profile?.steamid ?? "",
),
ownedGames = ownedGamesResponse.response.games;
ownedGames.sort((a, b) =>
a.playtime_forever < b.playtime_forever ? 1 : -1,
); // sortbyplaytime
// ownedGames.sort((a, b) =>
// a.rtime_last_played < b.rtime_last_played ? 1 : -1
// ) // sortbylastplayed
const enhancementLimit = import.meta.env.PROD ? 25 : 5; // limit the number of games to enhance, useful for larger libraries
const enhancedGamesPromises = ownedGames
.slice(0, enhancementLimit >= 0 ? enhancementLimit : undefined)
.map(
async (game) => {
if (!profile) {
return Promise.resolve(game);
}
const steamId = profile.steamid,
appId = game.appid;
const statResults = await steam.GetUserStatsForGame(steamId, appId);
const aResults = await steam.GetPlayerAchievements(steamId, appId);
const stats = statResults.playerstats.stats;
const statsCount = stats.length;
const achievements = aResults.playerstats.achievements;
achievements.sort((a, b) => (a.unlocktime < b.unlocktime ? 1 : -1));
const totalCount = achievements.length;
const achievedCount = achievements.filter(
(a) => a.achieved != 0,
).length;
const achievedPercentNum = (achievedCount / totalCount) * 100;
const achievedPercent = isNaN(achievedPercentNum)
? "0"
: achievedPercentNum.toFixed(2);
const newsResults = await steam.GetNewsForApp(appId);
const news = newsResults["appnews"]["newsitems"];
const newsCount = news.length;
return {
...game,
enhanced: true,
stats: stats,
statsCount: statsCount,
news: news,
newsCount,
achievements: achievements,
achievementsCountTotal: totalCount,
achievementsCountAchieved: achievedCount,
achievementsCountPercent: achievedPercent,
};
},
// profile ? enhanceGame(profile.steamid, g, srv) : Promise.resolve(g)
);
const enhancedGames = await Promise.allSettled(enhancedGamesPromises).then(
(results) =>
results.filter((g) => g.status === "fulfilled").map((g) => g.value),
);
return {
profile,
games: [
...enhancedGames,
...ownedGames.slice(
enhancementLimit >= 0 ? enhancementLimit : undefined,
),
],
};
};
const steamProfilePromises = playerSummaries.map(getProfileGames),
steamProfiles = await Promise.allSettled(steamProfilePromises).then(
(results) =>
results.filter((g) => g.status === "fulfilled").map((g) => g.value),
);
return steamProfiles;
}
export function printUnixDate(t: unknown): string {
if (t && typeof t === "number" && t > 0) {
const date = new Date(t * 1000);
return date.toLocaleDateString();
}
return "N/A";
}
async function handleFetch<T = unknown>(url: string): Promise<T> {
console.debug(`[Steam] :: Fetching ${url.split("?")[0]}...`);
const response = await fetch(url, { cache: "default" });
if (!response.ok || response.status < 200 || response.status > 299) {
throw new Error("Failed to load data.");
}
const data = (await response.json()) as T;
return data;
}