Fetch CS2 Inventory with Floats using CSInventoryAPI (NodeJS)

In an attempt to make CSInventoryAPI more accessible, I figured I would put together a quick article detailing how to successfully use our most popular endpoint, the inspect API (/api/v1/inspect)
The ?url= property
The only thing we need to obtain the advanced item info for a specific item is the inspect url, most easily found by navigating to your own inventory, clicking an item, and finding the "Inspect in Game..." button.

If you copy that link address, you will find something with a close resemblance to:
steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20S76561198166295458A38739886639D768787693327487156
Now, the most important parts of this url is what happens near the end, after the %20 (space).
There are three letters that separate the different sequences of numbers:
- S (steamid64, in this case 76561198166295458)
- A (assetid of the item, in this case 38739886639)
- D (a random number as far as I know, we will not have to worry about this).
Testing the inspect endpoint
Armed with this inspect link, you can try out the endpoint using the swagger UI available at https://csinventoryapi.com/docs.

If all works well, you should receive an output similar to this:

Fetching floats of items in an inventory using NodeJS
The crux of it is, when fetching an inventory (e.g., by using the https://csinventoryapi.com/api/v1/inventory endpoint), the inspect url does not come pre-populated with the fields required to inspect the item, specifically the S and A properties are only filled with placeholders.
The solutions to this is however relatively straightforward. The S property is set to the steamid64 of the item's owner, and the A property is populated with the item's assetid. Let us see how to do this in practice (all code will be available on GitHub):
Get user inventory
Firstly, we'll build a method to retrieve a user's inventory (also using CSInventoryAPI to avoid rate limits imposed by Steam):
import axios from 'axios';
const API_KEY = process.env.CSINVENTORYAPI_API_KEY;
if (!API_KEY) {
throw new Error('No API key provided');
}
/**
* Fetches the inventory of a user
*
* @param {string} steamid64 of the user
* @returns {Promise<Inventory>} the user's inventory
*/
const getUserInventory = async (steamid64) => {
const response = await axios.get(
`https://csinventoryapi.com/api/v1/inventory?api_key=${API_KEY}&steamid64=${steamid64}`
);
if (response.data.success !== 1) {
throw new Error('Failed to fetch inventory');
}
if (response.data.total_inventory_count === 0) {
throw new Error('No items found.');
}
if (!response.data.assets || !response.data.descriptions) {
throw new Error('No assets found.');
}
return response.data;
}
Simple method to fetch user inventory w/ some error handling.
Parse user inventory
Armed with this data, and given that the user have items in their inventory, we can go ahead and parse it into a more manageable array of items (Steam divides the assets and the descriptions, why this is a necessary step in most applications utilising inventory fetching).
/**
*
* @param {Inventory} inventory
* @returns {Promise<ParsedInventoryItem[]>}
*/
const parseInventory = async (inventory) => {
const parsedInventory = [];
for (let i = 0; i < inventory.assets.length; i++) {
const asset = inventory.assets[i];
const description = inventory.descriptions.find(d => d.classid === asset.classid);
if (!description) {
continue;
}
parsedInventory.push({
appid: asset.appid,
classid: asset.classid,
instanceid: asset.instanceid,
assetid: asset.assetid,
contextid: asset.contextid,
icon_url: description.icon_url,
tradable: description.tradable,
inspect_url: description.actions && description.actions[0] && description.actions[0].link || null,
name: description.name,
market_hash_name: description.market_hash_name,
name_color: description.name_color
});
}
return parsedInventory;
}
Some parsing is required to find the fields we are interested in (specifically the inspect_url, which might not always be present. A sticker capsule, for instance, does not have an inspect url).
If we now test running these method using the following code:
const inventory = await getUserInventory(steamid64);
const parsedInventory = await parseInventory(inventory);
console.log(parsedInventory);
We will see an array of items that look like this:
{
appid: 730,
classid: '720381639',
instanceid: '5837227973',
assetid: '38537743650',
contextid: '2',
icon_url: '-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXH5ApeO4YmlhxYQknCRvCo04DEVlxkKgpovbSsLQJf2PLacDBA5ciJlZG0hOPxNrfunWVY7sBOguzA45W73wy2_BY4Nm32cIWQcA4_ZVCC_1K4kLvohJTt7s6YmnRqs3Yh5y7Zlgv330_d1jhnvw',
tradable: 1,
inspect_url: 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20S%owner_steamid%A%assetid%D9223627464362234909',
name: '★ Karambit | Rust Coat',
market_hash_name: '★ Karambit | Rust Coat (Battle-Scarred)',
name_color: '8650AC'
}
If we look at the inspect url, we can see that we are missing the S and A properties that I mentioned earlier, and that they are filled with % placeholders. On a positive note, at least they tell us precisely what should be put in their place.
Populating the %owner_steamid% and %assetid% fields
The last step is to populate the fields in the item's inspect url, and actually fetch the floatvalue for each item in the parsed inventory.
/**
* Populates the float values of the items in the inventory
* @param {ParsedInventoryItem[]} parsedInventory
* @param {steamid64} steamid64
* @returns {Promise<ParsedInventoryItem[]>}
*/
const addFloatsToParsedInventory = async (parsedInventory, steamid64) => {
const parsedInventoryWithFloats = [];
for (const item of parsedInventory) {
try {
if (!item.inspect_url) {
console.error('No inspect url found for item:', item.market_hash_name);
continue;
}
// replace %owner_steamid% with the steamid64 of the user
// and %assetid% with the assetid of the item
const parsedInspectUrl = item.inspect_url
.replace('%owner_steamid%', steamid64)
.replace('%assetid%', item.assetid);
const response = await axios.get(
'https://csinventoryapi.com/api/v1/inspect?api_key=' + API_KEY + '&url=' + parsedInspectUrl
);
const {
floatvalue,
paintseed,
paintindex
} = response.data.iteminfo;
parsedInventoryWithFloats.push({
...item,
inspect_url: parsedInspectUrl,
floatvalue,
paintseed,
paintindex
});
await new Promise(r => setTimeout(r, 10000));
console.log('Fetched float values for item:', item.market_hash_name);
} catch (error) {
console.log(error);
console.error('Failed to fetch float values for item:', item.market_hash_name);
}
}
return parsedInventoryWithFloats;
}
First we replace the missing fields in the inspect_url, then we fetch the floatvalue using the API endpoint.
If we run this and print the inventory afterwards, we'll see floatvalues for the items that have that property, e.g.:
{
appid: 730,
classid: '1310017306',
instanceid: '5844343608',
assetid: '28124449669',
contextid: '2',
icon_url: '-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXH5ApeO4YmlhxYQknCRvCo04DEVlxkKgpoo6m1FBRp3_bGcjhQ09-jq5WYh8j_OrfdqWhe5sN4mOTE8bP5gVO8v106NT37LY-cJAZvZF-ErAC7wLi60MO57s7NwSBgvSgksynamEfmiRBJcKUx0nUflmj0',
tradable: 1,
inspect_url: 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20S76561198166295458A28124449669D17212839835523578275',
name: 'USP-S | Kill Confirmed',
market_hash_name: 'USP-S | Kill Confirmed (Minimal Wear)',
name_color: 'D2D2D2',
floatvalue: 0.13473990559577942,
paintseed: 461,
paintindex: 504
}
A note on rate limits
The timeout in the loop is to make sure that you do not hit the rate limit imposed by CSInventoryAPI. If you have a business/enterprise plan, the timeout duration can be adjusted accordingly.
If you have any questions, want further help with implementation, or want to start a new project, feel free to contact us.