@pd2trader/cli
v0.9.0
Published
Command-line client for the PD2 Trader API: Project Diablo 2 item prices, search, valuation, price checks, market depth, economy index, alerts, live stream, price cards and leaderboards in your terminal.
Maintainers
Readme
@pd2trader/cli
Project Diablo 2 market prices in your terminal, from pd2trader.com. Item prices, price history with sparklines, corruption prices, item search, stash valuation and currency conversion, price checks by sockets/corruption/stats, market depth, the economy index, webhook price alerts, a live event tail, price card images, economy categories, seasons and net-worth leaderboards. All prices are in HR (High Rune units: 1 HR = 1 Lo Rune, a Ber Rune is about 3 HR).
Built on the official @pd2trader/sdk. Needs Node.js 18.3 or newer.
Install
npm i -g @pd2trader/cli
pd2trader price "Ber Rune"
# or run it without installing
npx @pd2trader/cli price "Ber Rune"Authentication
Anonymous access still works but is deprecated and will be removed soon. Create an API key at https://pd2trader.com/developers/api-keys (sign in with your PD2 account), then save it:
pd2trader auth login # paste the key; input is hidden
pd2trader auth status # which key is used and where it comes from
pd2trader auth logout # remove the saved keyThe CLI sends the key as the X-API-Key header. It picks the key in this order:
--api-key <key>flagPD2TRADER_API_KEYenvironment variable- the key saved by
pd2trader auth loginin~/.config/pd2trader/config.json($XDG_CONFIG_HOME/pd2trader/config.jsonif set), written with mode0600
auth login checks the key against the API before saving it. In CI, pipe it in instead of typing it:
echo "$PD2TRADER_API_KEY" | pd2trader auth login, or just set PD2TRADER_API_KEY.
Without a key the CLI prints a one-line hint on stderr. --json or PD2TRADER_NO_HINTS=1 hides it.
PD2 sign-in (for your own stash)
stash export reads your own stash, so it needs a Project Diablo 2 sign-in on top of the API key:
pd2trader login # opens the PD2 sign-in page; --no-browser only prints the link
pd2trader whoami # PD2 user, linked game accounts and token expiry
pd2trader logout # forget the PD2 sign-in (the API key stays)login saves the PD2 access and refresh tokens plus your linked game accounts in the same config
file (mode 0600). The CLI sends the PD2 token as Authorization: Bearer (the API key still goes in
X-API-Key) and refreshes it automatically when it is about to expire or the server answers 401,
saving the new tokens. If the refresh is rejected, the tokens are removed and you are asked to run
pd2trader login again.
Global options
| Option | Description |
| --- | --- |
| --api-key <KEY> | API key (overrides PD2TRADER_API_KEY and the saved key) |
| --hardcore | Hardcore prices (default: softcore) |
| --non-ladder | Non-ladder (default: ladder). Item prices are only collected for ladder softcore and hardcore, so price commands find little or no non-ladder data |
| --json | Print the raw API response as JSON (no tables, no hints) |
| --base-url <URL> | API origin (default https://pd2trader.com, env PD2TRADER_BASE_URL) |
| --no-color | Disable colours. NO_COLOR=1 works too; colours are off when output is piped |
| -h, --help | Help, also per command: pd2trader <command> --help |
| -v, --version | CLI version |
Commands
Example outputs below show the format; the numbers change constantly.
price <item...>
Average, median, 7-day moving average (7d MA) and 7-day change for one or more items. Names are
exact and case-insensitive; quote names with spaces. Options: --hours N, --quality Unique|Set.
$ pd2trader price "Ber Rune" "Jah Rune" "Harlequin Crest"
Ladder softcore · prices in HR
Item Avg Median 7d MA 7d Samples
--------------- ---- ------ ----- ----- -------
Ber Rune 1.03 1.00 1.00 +2.1% 3,183
Jah Rune 0.85 0.85 0.86 -1.4% 3,706
Harlequin Crest 0.42 0.40 0.41 +5.0% 214batch <baseCode...>
Prices for many base codes in one request. Codes come from arguments and/or --file <path>
(one per line, commas/spaces also fine, # comments, - = stdin). Codes without data are listed on stderr.
$ pd2trader batch r30 r31 r32 r33
Ladder softcore · prices in HR
Item Base Avg Median 7d MA 7d Samples
--------- ---- ---- ------ ----- ----- -------
Ber Rune r30 1.03 1.00 1.00 +2.1% 3,183
Jah Rune r31 0.85 0.85 0.86 -1.4% 3,706
Cham Rune r32 0.21 0.20 0.20 +0.5% 3,654
Zod Rune r33 1.18 1.15 1.15 +0.8% 2,617history <baseCode>
Daily price history, oldest first (today is not included). --days N (default 30, max 365),
--type CODE, and --chart for a sparkline instead of the table.
$ pd2trader history r30 --days 5
r30 (rune) · Ladder softcore · 5 days · prices in HR
Date Avg 7d MA Listings
---------- ---- ----- --------
2026-10-11 0.95 0.98 145
2026-10-12 0.99 0.99 137
2026-10-13 1.02 0.99 129
2026-10-14 0.98 1.00 121
2026-10-15 0.97 1.00 158
$ pd2trader history r30 --days 30 --chart
r30 (rune) · Ladder softcore · 30 days · prices in HR
7d MA ▁▁▁▁▂▂▂▂▃▃▃▄▄▄▄▅▅▅▅▆▆▆▇▇▇▇████
Daily ▁▃▁▁▃▄▁▂▄▄▂▃▅▄▃▅▆▄▄▇▆▄▆█▆▅▇█▇▆
2026-09-16 2026-10-15
Latest 1.00 HR (7d MA), 0.97 HR daily
Range 0.88 – 1.00 HR
Change +13.6%
Listings 4,260corruptions <item>
Prices per corruption (and socket count) for a unique or set item, highest median first.
Option: --hours N (default 168).
$ pd2trader corruptions "Harlequin Crest"
Harlequin Crest Ladder softcore · prices in HR
Corruption Avg Median Samples
--------------------- ---- ------ -------
Sockets 0.65 0.60 38
└ 1 socket 0.45 0.45 21
└ 2 sockets 0.90 0.85 17
to All Skills 0.80 0.75 12
Increase Maximum Life 0.48 0.45 9check <item>
Price check by properties: an estimated range (25th percentile / median / 75th percentile) from recent
listings that match, with confidence, the filters that were and were not applied, and the closest
comparable listings. Options: --sockets N, --eth, --corrupted / --not-corrupted,
--corruption NAME (implies corrupted), --stat name=value (repeatable; a numeric name is a PD2 stat id),
--quality Unique|Set, --hours N (1–720, default 168).
Listings only store sockets, ethereal, corruption, quality and the Rainbow Facet / Hellfire Torch /
Spirit Ward variant. Any other stat is listed under "Not applied" and caps confidence at medium.
Exits with code 1 when there is no estimate.
$ pd2trader check "Harlequin Crest" --corrupted --corruption item_numsockets --sockets 1
Harlequin Crest harlequin_crest Ladder softcore · last 168h · prices in HR
Estimate 2.00 HR (range 1.60 – 2.50)
Confidence medium
Basis exact · 7 of 64 listings · 1 outliers removed
Matched on Sockets = 1 (±1 socket counts as a near match); Corrupted only; Corruption includes Sockets
Comparable listings
Price Match Listed Soc Eth Corruption
----- ----- -------------------- --- --- ----------
2.00 100% 2026-10-15 18:42 UTC 1 Socketspd2trader check "Rainbow Facet" --stat damageType=cold --stat damagePercent=5 --stat piercePercent=5
pd2trader check "Hellfire Torch" --stat class=Sorceressappraise <slot|baseCode> --stat key=value... · appraise --file PATH
Rare-item appraisal, the same as the stash view and the desktop overlay: a verdict (keep = probably valuable, check = worth a look, vendor = likely low value), the item's rank among rares of its slot, its key stats (★ ×1.25–2, ★★ ×2–4, ★★★ ×4+ price multiplier, with near misses and corruption-only stats called out) and the usual asking price of similar rares. Exits with code 1 when the item can't be appraised.
- From stats: a slot (
ring,amul,circ,jewl,glov,boot,belt,tors,helm,shield,quiver,weapon) or base code (rin,ci2, …), and--stat key=valueper stat. Keys come frompd2trader rare-guide <slot> --options.--corrupted-stat KEYmarks a stat that came from corruption (implies--corrupted);--eth,--sockets N. --phase early|late|seasonappraises the item for that season phase instead of today (stars, rank and price band of that phase).- From the game:
--file item.json(or--file -for stdin) with the JSON Project Diablo 2 copies on Ctrl+C. This also prints the item's earlier market listings when it has more than three stats. --hardcoreuses hardcore listing prices; verdict and stars still come from softcore data.- Many items: a
--filewith several items, one per blank-line-separated block, is appraised as a batch. With--json-inputthe file is a JSON array of item texts or{ "text": … }/{ "item": … }objects.--phaseapplies to every item. The table shows each item's position (#, from 1), verdict, rank, usual price band and top key stat; invalid entries show their error and don't stop the rest.--jsonprints the raw batch response (indexis 0-based there). Exits with code 1 when nothing could be appraised. Every 10 items cost one request of the rate limit.
$ pd2trader appraise ring --stat item_fastercastrate=20 --stat all_resist=11 --corrupted-stat item_fastercastrate
Ring Ladder softcore · late season · s13-q50-2026-10-17
Verdict CHECK (high priority)
Rank top 44% of rings (56th percentile ±18)
Usually listed 0.75–1.00 HR rings at this rank · this season
Stat Why
--- -------------------- -----------------------------------------------
★★★ 20% Faster Cast Rate ×5.7 · only via corruption on rings · corrupted
All Resistances +11%$ pd2trader appraise --file stash-rares.txt
Ladder softcore
# Verdict Rank Usually listed Top key stat
- ------- --------------- -------------- -------------------------
1 KEEP top 4% rings 2.00–4.00 HR ★★★ +20% Faster Cast Rate
2 NONE not a rare
3 INVALID text must be a non-empty string
1 appraised · 1 not appraised · 1 invalidrare-guide [slot]
What multiplies a rare's asking price, from a season of market listings. Without a slot: every slot with
its strongest key stat. With a slot: key stats with the value they count from, price tiers (? = few
listings), whether they're rising or falling, item archetypes and the price ladder (hardcore with
--hardcore). --phase early|late|season picks the season phase (default late); --options lists every
stat key appraise accepts for the slot.
pd2trader rare-guide ring
pd2trader rare-guide circ --phase early
pd2trader rare-guide weapon --optionsrare-history <fingerprint>
Earlier PD2 market listings of one item by its fingerprint (the id from appraise --json): times listed,
distinct sellers and each listing's price, marking where the item changed owner. Items with three or fewer
stats share fingerprints with unrelated items, so their history is not shown. Exits with code 1 when never
listed.
search <query>
Fuzzy item search by base code, name, alias or nickname (typos are fine). The Code column is the base code
every other command accepts. Options: --category CATEGORY, --limit N (1–50, default 10).
$ pd2trader search shako
1 hit for 'shako'
# Item Code Category Match
- --------------- --------------- -------- -----
1 Harlequin Crest harlequin_crest uniques aliascatalog
Every price-tracked item with its base code, type code and quality, sorted by category then name.
Option: --category CATEGORY. Categories: runes, currency, ubers, gems, mats, rainbow-facets,
maps, charms, uniques, sets, equipment, other.
value <item[:qty]...>
Total HR value of a list of items, with Net Worth's rules: market prices with at least 10 samples are ok,
thinner markets are low (shown, but left out of the total), El–Fal runes and missing high runes use
fixed values. Items are names, aliases or base codes; :N sets a quantity. Duplicates are merged and
unknown items are listed on stderr. Options: --file PATH (- for stdin), --hours N,
--rune-pricing dynamic|fixed.
Files are either text (one item[:qty] per line, # comments) or JSON
(["ber:2", {"item": "jah", "qty": 1}] or {"items": [...]}). Up to 500 items.
$ pd2trader value ber:3 shako
Ladder softcore · last 168h · dynamic rune prices · prices of 2026-10-16 · values in HR
Item Qty Each Value Conf Samples
------------------------------- --- ---- ----- ---- -------
Ber Rune r30 3 1.02 3.06 ok 412
Harlequin Crest harlequin_crest 1 0.40 0.40 low 7
Total 3.06 HR
Uncertain 0.40 HR (low confidence, not in total)--rune-pricing (on value, convert and note) sets how runes are valued: dynamic (default) uses the
market price for runes with at least 10 listings and the fixed table for El–Fal and thin markets; fixed values
every rune at the fixed table (Ist 0.15, Vex 0.5, Lo 1, Jah 2, Ber 3, …). Other items are always valued at market.
The caption shows which one was used (dynamic rune prices / fixed rune prices).
convert <amount> <from> <to>
Converts an amount of one item into another from their HR values. Use hr on either side for the HR unit.
Options: --hours N, --rune-pricing dynamic|fixed.
$ pd2trader convert 10 ist ber
Ladder softcore · last 168h · dynamic rune prices · prices of 2026-10-16
10 Ist Rune = 1.4706 Ber Rune
Rate 1 Ist Rune = 0.147059 Ber Rune
Inverse 1 Ber Rune = 6.8 Ist Rune
From 0.15 HR each (fixed)
To 1.02 HR each (ok)
Confidence oknote <text...>
Reads the asking price from a free-text trade note (1 to 500 characters; several arguments are joined with
spaces). The site price parser and a note-price model both read the note, and a second model picks the more
likely value. Options: --hours N, --rune-pricing dynamic|fixed. Exits with code 1 when no price is stated.
- Probability is the calibrated chance that the stated price is the seller's asking price: 80% and above
is reliable (80-90% is right about 86% of the time, 90-95% about 91%, above 95% about 97%). 40-80% is
marked as a rough read. Below 40%, or when the note states no price, it prints
No price stated. - Stated uses the fixed rune/WSS table (1 WSS = 0.01 HR). The item table and Market total value the
price items at market prices (with
--rune-pricing fixed, runes at the fixed table, so the total matches Stated for rune/WSS/HR notes, except Lem, which the note reader counts as 0.02). - A current offer (
c/o) is not the asking price; a BIN wins (c/o 2 bin 3reads 3 HR).
$ pd2trader note "wts 2 ist + pul"
Ladder softcore · last 168h · dynamic rune prices · prices of 2026-10-20
Stated 0.33 HR
Probability 95%
Source parser
Matched "2 ist + pul"
Item Qty Each Value Conf Samples
------------ --- ----- ----- ----- -------
Ist Rune r24 2 0.15 0.30 fixed 0
Pul Rune r21 1 0.030 0.030 fixed 0
Market 0.33 HR (items at market prices)
$ pd2trader note offer
No price statedmarket <item>
Market depth over the last 7 or 30 complete UTC days (--days 7|30): listings observed, listings still
active, new listings per day (sparkline) and the ask distribution (lowest, quartiles, highest, IQR spread).
There are no bids, and this endpoint does not compute time-to-sell or sell-through.
$ pd2trader market ber
Ber Rune r30 Ladder softcore · 7 days · prices in HR
Window 2026-10-09 – 2026-10-16 (data as of 2026-10-16 04:00 UTC)
Confidence high (412 listings)
Observed 412 listings updated in the window
Active 137 listings updated in the last 48h
New 198 listings first seen in the window ▁▁▂▁█▅▃
Asks Count Lowest P25 Median P75 Highest Spread (IQR)
-------- ----- ------ ---- ------ ---- ------- ------------
Observed 412 0.82 0.95 1.00 1.10 3.00 0.15 (15.0%)
Active 137 0.85 0.95 0.98 1.05 2.50 0.10 (10.2%)listings [item name...] · listing <id>
Search live (or, with --archive, sold) PD2 market listings with the overlay's filters. Positional
words match the item name. Options: --type CODE, --base CODE, --rarity R[,R] (Normal, Superior, Magic,
Rare, Crafted, Set, Unique, Runeword), --price TEXT (price note), --hr-min N, --hr-max N,
--hr-max-exclusive (price < --hr-max),
--stat KEY[:MIN[:MAX]] (PD2 property key, repeatable; without bounds the item must have the stat; the extended
grammar works too, e.g. item_skillongethit{*,44}[0]:15 or firedam|firemindam{4..}[1]:14),
--pseudo ID[:MIN[:MAX]] (e.g. pseudo_total_res, repeatable; without bounds value > 0), --weight KEY=W
(repeatable; weights a matching stat/pseudo row or adds a row for that stat, which then also requires it, and
shows Score/Value columns), --sort bumped|price|score|value or --sort stat:KEY[:asc|desc] (one property key
or pseudo id; listings without the stat come last), --asc, --days N (≤365), --limit N (1-100, default 25),
--page N (from 1), --facets F[,F] (also count the matches per value of quality, baseCode, typeCode,
sockets, ethereal, corrupted, runeword or price; a facet on a dimension you filter on is counted without
that filter, so it lists the alternatives). listing <id> shows one listing with its status, modifiers and pseudo stats.
$ pd2trader listings --type circ --rarity rare --pseudo pseudo_total_res:60 --stat item_fastercastrate:20
$ pd2trader listings --type ring --rarity rare --weight pseudo_life=1 --weight pseudo_total_res=2 --sort score
$ pd2trader listings --type circ --sort stat:item_fastercastrate
$ pd2trader listings --type amul --sort stat:pseudo_total_res:asc
$ pd2trader listings --type circ --rarity unique --facets quality,sockets,price
$ pd2trader listings harlequin crest --sort price --asc --page 2
$ pd2trader listing 6712f0c2a1b2c3d4e5f60718items <category>
Every priced item of an economy category, as shown on pd2trader.com. Options:
--quality Unique|Set, --type CODE, --hours N, --low-confidence (include items with fewer than 10 samples).
When an item has fewer than 10 samples in the window, the server counts them over the last 30 days, the
season or the last 30 days of the previous season instead; the Window column then says which (30d,
season, last season). Price stays the 7-day moving average.
Categories: currency, ubers, runes, charms, rainbow-facets, maps-t5, equipment-armor,
equipment-belt, equipment-boots, equipment-weapon, equipment-jewelry, equipment-gloves,
equipment-helm, equipment-shield, equipment-quivers.
$ pd2trader items equipment-helm --quality Unique
Equipment - Helm · Ladder softcore · last 168h · prices in HR · updated 2026-10-16 04:00 UTC
# Item Price 7d Samples
- ----------------- ----- ----- -------
1 Griffon's Eye 1.20 +4.3% 57
2 Harlequin Crest 0.41 +5.0% 214
3 Andariel's Visage 0.15 -2.0% 33runes
Shortcut for items runes, sorted by price, highest first. Option: --low-confidence.
$ pd2trader runes
Runes · Ladder softcore · last 168h · prices in HR · updated 2026-10-16 04:00 UTC
# Item Price 7d Samples
- -------- ----- ----- -------
1 Zod Rune 1.15 +0.8% 2,617
2 Ber Rune 1.00 +2.1% 3,183
3 Jah Rune 0.86 -1.4% 3,706index
The economy index: the daily HR cost of a fixed basket of goods, where 100 is its cost at season start
(below 100 the goods got cheaper). Shows the latest value with 1-day, 7-day and season change, the last
14 days (or a sparkline of the whole season with --chart) and the components. Options:
--basket high-runes|mid-runes|endgame|all (default high-runes), --season ID, --chart.
Only ladder is indexed.
$ pd2trader index --chart --season s13
high-runes index · s13 · Ladder softcore · 100 = 2026-04-23
Latest 62.41 on 2026-10-15
1d -1.2%
7d -4.7%
Season -37.6%
Index ███▇▇▇▅▆▆▄▅▅▃▃▄▂▂▃▁▁
2026-04-23 2026-10-15
range 61.00 – 100.00
Component Weight Base Now Change
------------ ------ ---- ---- ------
Ber Rune r30 12.5% 3.00 1.85 -38.3%
...card <item>
Downloads the shareable price card (price, 7-day change, trend) of an item. Saves <baseCode>-card.png
by default; --out FILE picks the file (- writes the image to stdout) and --url prints the image URL
instead, for Discord or Open Graph embeds. Options: --size og|compact (1200×630 or 600×200),
--theme dark|light, --format png|svg.
$ pd2trader card ber
Saved Ber Rune card to r30-card.png (48 KB)
$ pd2trader card ber --url --size compact
https://pd2trader.com/api/cards/item/r30.png?size=compact&isLadder=true&isHardcore=falsemarket and card take names, aliases or base codes and use the top search hit. A name that matches
several items equally well (e.g. torch) is an error that lists the candidates.
watch
Live tail of the event stream (/api/stream): prices after every price update, feed for new finds of
players with a public profile, badges for newly earned badges, and PD2 market listings
(market.listing.created|updated|sold|removed|expired, printed as name/base · price · seller · mode). One line per
event with its UTC receive time; reconnects automatically; Ctrl+C exits with code 0. Option:
--events prices,feed,badges,market (market = all five listing events; default: prices, feed, badges, as
market events are opt-in). With --json every event, including the initial hello, is one JSON line (NDJSON):
{ "type", "id", "data", "receivedAt" }.
$ pd2trader watch
Connected to https://pd2trader.com/api/stream · prices, feed, badges · Ctrl+C to stop
18:00:41Z prices Prices updated · Ladder softcore, Ladder hardcore
18:04:12Z feed Gheed (gheed) found Ber Rune ×2 2.00 HR · Ladder softcore
18:04:12Z badges Gheed (gheed) earned first-ber · Ladder softcore$ pd2trader watch --events market
18:05:02Z market.listing.created Harlequin Crest/Shako · 6.00 HR · Errol · Ladder softcoresearches list | create | update | delete | alerts
Saved market searches of your PD2 login (pd2trader login first; sent as Authorization: Bearer). A search
with --alert is watched: every new or bumped listing that matches becomes an alert, once per listing, kept 30
days. Limits: 25 saved searches, 10 alerting.
searches list: your saved searches and the caps.searches create --filters JSON [--name NAME] [--alert]:--filtersis the market search filters object.searches update <id> [--name NAME] [--filters JSON] [--alert | --no-alert]searches delete <id>searches alerts [--since ISO] [--limit 1-100]: recent alerts, newest first, as listing lines.
alerts list | create | update | delete | test
Alerts that call a webhook. They belong to your API key, so a key is required. Discord webhook URLs get an
embed; any other HTTPS URL gets a JSON body signed with HMAC-SHA256 (X-PD2Trader-Signature: sha256=<hex>,
verify it with verifyWebhookSignature from @pd2trader/sdk).
Price alerts fire after a price update when
<metric> <op> <threshold>holds, at most once per cooldown.Rare item alerts (
--rare) fire for every newly listed rare of a slot that has all the required key stats, once per item.alerts list: your alerts with their rule, mode and last delivery.alerts create --item ITEM --metric average|median|change7d --op lt|lte|gt|gte --threshold N --webhook URL [--cooldown H] [--name NAME]: creates a price alert and prints its signing secret once.--hardcore/--non-ladderset its mode. The threshold is in HR foraverage/medianand in percent forchange7d; negative values need=(--threshold=-10).--opalso accepts<,<=,>,>=(quote them). Cooldown is 1–720 hours (default 12).alerts create --rare --slot SLOT --stat key=min... --webhook URL [--max-hr N] [--min-stars 1-3] [--name NAME]: creates a rare item alert (1–4--statrequirements, keys frompd2trader rare-guide <slot>).--max-hr(greater than 0) skips listings priced higher (and unpriced ones);--min-starsrequires every listed stat to rate at least that many stars.alerts update <id>with any of the create flags, plus--enable/--disable. On a rare alert, one criteria flag (e.g.--max-hr 1.5) keeps the others.alerts delete <id>.alerts test <id>: sends a test delivery now and exits with code 1 if it fails.
$ pd2trader alerts create --item ber --metric median --op lte --threshold 0.9 --webhook https://discord.com/api/webhooks/…
Alert created.
ID 6710a2f4c1e5b8a9d4f3e201
Name Ber Rune
Item Ber Rune r30
Rule median ≤ 0.90 HR
...
Signing secret: whsec_3f9c1a7b…seasons
Started seasons, newest first; * marks the current one.
$ pd2trader seasons
ID Season Start End
- --- --------------- ---------- ----------
* s14 Alliance (S14) 2026-10-23
s13 Betrayal (S13) 2026-04-23 2026-10-23
s12 Suffering (S12) 2025-10-25 2026-04-22leaderboard
Net-worth leaderboard of opted-in players. Options: --board gained|wealth|session (default gained),
--timeframe 7d|30d|season (default season), --basis liquid|total (default liquid),
--season ID, --limit N (1–100, default 50), --offset N.
The outputs below are illustrative.
$ pd2trader leaderboard --board wealth --limit 3
wealth board · liquid · season · s14 · Ladder softcore · 1,204 players · updated 2026-11-02
Rank ± Player Slug Value (HR)
---- --- ------- ------- ----------
1 – Deckard deckard 412.50
2 ▲1 Akara akara 398.00
3 new Kashya kashya 377.25profile <slug>
A player's public profile: net worth, trend sparkline, ranks, best session, badges, recent finds and,
if shared, top holdings. Options: --season ID, --basis, --timeframe. Without --hardcore /
--non-ladder the player's own mode is used.
$ pd2trader profile deckard
Deckard (deckard)
Season Season 14 (S14)
Mode Ladder softcore
Liquid 412.50 HR
Total 530.00 HR
Trend ▁▁▂▃▄▅▆▇██ liquid, 10 days
Percentile 99 (share of players at or below this net worth)
Rank gained #4 of 1,204 ▲2 88.00 HR
Rank wealth #1 of 1,204 – 412.50 HR
Rank session —
Best session 2h 15m, +12.30 HR (5.47 HR/h) on 2026-10-30
Badges first-ber, hr-100
Recent finds
When Item Qty Value (HR)
-------------------- -------- --- ----------
2026-11-02 18:41 UTC Ber Rune +1 1.00feed
Recent notable finds of opted-in players, newest first. Options: --limit N, --season ID.
$ pd2trader feed --limit 2
Recent finds · Ladder softcore · values in HR
When Player Item Qty Value (HR)
-------------------- ------- -------- --- ----------
2026-11-02 18:41 UTC Deckard Ber Rune +1 1.00
2026-11-02 18:12 UTC Akara Zod Rune +2 2.30builds usage | characters | character <name>
Ladder characters (level 80+) crawled from PD2's game API. --hardcore selects hardcore (ladder only, so
--non-ladder is rejected). Data can lag when PD2's game endpoints are down.
| Option | Description |
| --- | --- |
| --season <ID> | Season id, e.g. s14 (default: current) |
| --class <NAME> | Any of; repeat or comma-separate (--class Sorceress,Paladin) |
| --skill <NAME[:N]> | All of; Name:N = at least N hard points (default 1) |
| --item <KEY> | All of; catalog keys like unique:griffon_s_eye, rw:infinity |
| --merc-type <NAME> | Any of, e.g. "Defensive Auras" |
| --merc-item <KEY> | All of (merc gear) |
| --min-level <N>, --max-level <N> | 80–99 |
| --max-cost <HR> | Maximum gear value; characters without a gear value are left out |
| --name <TEXT> | characters: name contains TEXT (case-insensitive) |
| --sort, --dir, --page | characters: level (default), life, gearValueHr, lastUpdated; desc (default) or asc; page number |
| -n, --limit <N> | usage: rows per table (1–50, default 10); characters: page size (1–50, default 25) |
usage prints class, skill (20+ hard points), item, merc and merc item shares; characters one page of
matching characters with level, life, top skills and gear value; character the skills, attributes, gear and
merc gear with prices (exit code 1 for an unknown name).
pd2trader builds usage --class Sorceress --skill Nova:20
pd2trader builds characters --item unique:griffon_s_eye --item rw:infinity --sort gearValueHr
pd2trader builds character Butzfire --json | jq '.equipped[] | [.slot, .name, .priceHr]'sim run | job | cancel | report | reports | delete | import
Horadric Sim, the PD2 damage simulator. With an API key, jobs run as the key owner's PD2 account: its tier limits (free, supporter or beta tester), its concurrent-job limit (shared with its browser session) and its reports. Without a key only Quick Sim is available.
| Subcommand | Description |
| --- | --- |
| run <spec.json\|profile.txt> | Submit a job: a .json JobSpec, or any other file as profile.txt text (the horadric-sim format). --bundle <ID:HASH> pins a profile without a bundle= line (default: the newest data bundle). A spec the current engine already reported on is served from that report (shown as Reused; nothing is simulated); --force simulates it again. --wait polls (progress on stderr) and prints the report; --timeout <S> gives up after S seconds (default 600) |
| job <id> | Status, progress and report link of a job |
| cancel <id> | Cancel a queued or running job you submitted |
| report <slug\|url> | DPS, kill time and death rate (mean ± 95% CI) per profile and the delta vs the baseline; takes a slug or a …/sim/r/<slug> permalink |
| reports [--page N] [--limit N] | Reports of your key's account, newest first (API key required) |
| delete <slug\|url> | Delete one of your reports (API key required) |
| import <character> | A public PD2 character as a sim build, with skills PD2's API leaves out (e.g. Split Throw) and notes; --json prints the build for a JobSpec |
pd2trader sim run blizzard.profile.txt --wait
pd2trader sim report https://pd2trader.com/sim/r/q8Zr0Lw3VvB2xT9mN4kD1A
pd2trader sim import YozzSorc --json | jq .build > build.jsonauth login | status | logout
See Authentication. auth status exits with code 1 when no key is configured.
login · logout · whoami
See PD2 sign-in. whoami exits with code 1 when you are not signed in.
stash export
Export every item in one of your stashes with its HR value. Needs pd2trader login. Nothing is saved
on the server.
| Option | Description |
| --- | --- |
| --account <NAME> | Game account to export. Default: your only linked account, else your PD2 preferred account, else a prompt (interactive) or an error listing the choices |
| --format <csv\|json\|table> | Default: from the --out extension (.csv, .json); else a table on a terminal and JSON when piped (--json also picks JSON) |
| -o, --out <FILE> | Write to a file instead of stdout. CSV files start with a UTF-8 BOM so spreadsheet apps read them correctly |
| --hardcore, --non-ladder | Which stash (default: ladder softcore) |
| --no-prices | Skip pricing (faster; value columns are empty) |
| --min-value <HR> | Only items worth at least HR; drops unpriced items and recomputes the totals |
| --sort <value\|name\|tab> | Sort items. Default: stash order for CSV/JSON, value for the table |
The table shows the totals and the top 20 items (tab, item, quantity, unit and total value,
price confidence). For CSV and JSON a one-line summary (items, priced, total HR) goes to stderr.
CSV columns: tab,x,y,name,baseName,baseCode,typeCode,quality,quantity,ilvl,ethereal,sockets,socketed,corrupted,corruptions,stats,unitHr,valueHr,confidence
(lists joined with |, stats as name: value).
$ pd2trader stash export --out stash.csv
412 items · 380 priced · total 57.31 HR · MainAcc, Ladder softcore → stash.csv
$ pd2trader stash export --account MuleAcc --min-value 1 --sort value --format json | jq '.items[].name'Scripting
--json prints the API response unchanged (pretty-printed), so it pipes straight into jq.
Tables never go to stdout in JSON mode, and hints, warnings and errors always go to stderr.
# Ber price as a bare number
pd2trader price "Ber Rune" --json | jq '.movingAverage7Days // .medianPrice'
# Several items -> TSV (several names print an array)
pd2trader price "Ber Rune" "Jah Rune" --json | jq -r '.[] | [.itemName, .medianPrice] | @tsv'
# Watchlist file -> CSV
pd2trader batch --file watchlist.txt --json | jq -r '.data[] | [.baseCode, .itemName, .medianPrice] | @csv'
# 90 days of Ber history as CSV
pd2trader history r30 --days 90 --json | jq -r '.data[] | [.date[:10], .avgPrice, .movingAverage] | @csv'
# Top 5 runes in hardcore
pd2trader runes --hardcore --json | jq -r '.items[:5][] | "\(.name)\t\(.price)"'
# Alert when Ber moves more than 10% in a week
pct=$(pd2trader price "Ber Rune" --json | jq '.priceChange7Days.changePercent // 0')
awk -v p="$pct" 'BEGIN { exit !(p > 10 || p < -10) }' && echo "Ber moved ${pct}%"
# Stash value as a bare number
pd2trader value --file stash.txt --json | jq .totalHr
# Median ask of active Ber listings
pd2trader market ber --json | jq .activeAsks.median
# Log every price update
pd2trader watch --events prices --json | jq -r 'select(.type == "prices") | .data.completedAt'Exit codes
| Code | Meaning |
| --- | --- |
| 0 | Success (also watch stopped with Ctrl+C) |
| 1 | API or network error, invalid key (401), rate limit (429), item not found or ambiguous, no data, no estimate (check), failed test delivery (alerts test), alerts without an API key |
| 2 | Usage error: unknown command or option, missing or invalid argument |
Errors are short and actionable: a 401 tells you where the key came from and to run
pd2trader auth login; a 429 tells you how many seconds to wait (Retry-After).
What's new in 0.9.0
sim run --forcesimulates a spec even when an identical job already has a report (the server reuses it by default;runshows Reused).sim cancel <id>cancels a queued or running job.sim importlists hidden skills and notes.listings --facets F[,F]: facet counts (quality, bases, types, sockets, flags, price histogram) with the results.builds characters --name TEXT: name contains.- Requires
@pd2trader/sdk0.9.0 (sim.cancel,SimSubmitOptions.force,BuildCharactersOptions.q).
What's new in 0.8.0
sim run <spec.json|profile.txt> [--wait],sim job,sim report,sim reports,sim delete,sim import: Horadric Sim jobs and reports with your API key (the key owner's tier and limits). Requires@pd2trader/sdk0.8.0 (sim).appraise --phasenow also works for a single item (stats or a one-item--file); it no longer forces the batch endpoint.listings: the HR column shows the effective HR ask (price_hr: the seller's HR price, else the HR the price note states), the value--hr-min,--hr-maxand--sort priceuse.alerts --max-hrrejects 0 like the server;builds usage --limitis capped at 50 (rows the server returns).- Requires
@pd2trader/sdk0.8.0 (RareAppraiseInput.phase,MarketListing.price_hr).
What's new in 0.7.0
builds usage | characters | character <name>: ladder build usage, matching characters and a character's gear with prices. Requires@pd2trader/sdk0.7.0 (builds).
What's new in 0.6.0
--rune-pricing dynamic|fixedonvalue,convertandnote:fixedvalues every rune at the fixed rune table. The caption shows the rune pricing used. Requires@pd2trader/sdk0.6.0 (runePricing).
What's new in 0.5.0
note <text...>: read the asking price from a trade note (stated HR, calibrated probability, source, matched text and the price items at market). Requires@pd2trader/sdk0.5.0 (tools.valuateNote).
What's new in 0.4.1
- Requires
@pd2trader/sdk0.4.1:appraiseuses the new/api/rares/appraisalendpoint (0.4.0 got 404s after the server moved rare appraisal).
What's new in 0.4.0
appraise(slot/base +--stat, or the game's Ctrl+C JSON with--file),rare-guide,rare-history.appraise --filewith several blank-line-separated items (or a--json-inputarray) appraises them as one batch (SDKrares.appraiseMany): a table numbered from 1,--phase,--jsonfor the raw response (0-basedindex).
Links
- Docs: https://pd2trader.com/developers/cli
- API reference: https://pd2trader.com/developers/reference
- SDK:
@pd2trader/sdk
