Feedback

API

Every price on this site is also a file. No key, no account, no signup — just JSON on a CDN, rebuilt daily from the same snapshot that builds the pages. User-Agent policy is the one hardening rule; a real integration is unaffected by it.

If you can read a URL, you can use this.

The coverage numbers on this page are rendered from the current build. The example payloads show the current response shapes; their illustrative values move daily.

First call

curl -s https://palworldprices.com/api/v1/index.json
{
 "source": "Palworld Prices",
 "url": "https://palworldprices.com/api/v1/index.json",
 "license": "free with attribution",
 "attribution": "Data: Palworld Prices (palworldprices.com) — completed-sale prices with confidence tiers",
 "terms": "Free to use, including commercially, with attribution and a link. Please do not imply endorsement, and please send a descriptive User-Agent. Card images are NOT part of this API.",
 "asOf": "2026-09-17T22:30:01.508Z",
 "stale": false,
 "honesty": "Prices are medians of COMPLETED SALES, never asking prices. …",
 "description": "A free, static, key-less JSON API over completed-sale prices for the Palworld trading card game. …",
 "endpoints": [ … ],
 "coverage": {
  "cardsTracked": 280,
  "withPublishedPrice": 254,
  "confidentlyPriced": 218,
  "note": "Most cards in a game this young have no completed sale yet. Those carry no price, by design."
 },
 "confidenceTiers": { … },
 "stability": "Additive changes only within v1: fields may be added, existing fields will not be removed or repurposed. Breaking changes get a new version prefix.",
 "updated": "Rebuilt daily. Every payload carries asOf and a stale flag — freshness is disclosed, never promised."
}

index.json describes itself. It is the only URL you need to memorise.

Endpoints

URLReturns
/api/v1/index.jsonThis directory: endpoints, coverage counts, tier definitions
/api/v1/cards.jsonAll 280 tracked printings with identity and headline price
/api/v1/cards/{cardId}.jsonOne printing: headline, 7d/30d/90d aggregates, receipt count, current asks
/api/v1/404.jsonThe error document. See Missing cards
/feeds/latest-sales.xmlRSS: newest source-attributed completed sales, with a public listing link when available
/feeds/movers.xmlRSS: 7d medians that moved against 30d, confidence-gated
/feeds/changelog.xmlRSS: what changed on the site, in plain language
/llms.txtWhat this site is, in the format assistants read

All served over HTTPS with Access-Control-Allow-Origin: *. Browser code can fetch them directly.

The envelope

Every JSON payload opens with the same block, so a response is self-describing no matter where it ends up.

FieldMeaning
source"Palworld Prices"
urlThis payload's own canonical address
license"free with attribution"
attributionThe exact line you must publish. See Attribution
termsLicence in one sentence
asOfISO timestamp of the build this data came from
staletrue when the snapshot is older than the freshness bar
honestyThe one rule you must not break when re-displaying these numbers

Check stale before you publish anything. It is the site's own judgment on its own data.

The honesty contract, in JSON

This is the part that makes the API different from a price feed.

A card's price lives in headline, and headline.kind has exactly three values. The shape of the object changes with the tier — that is deliberate, so a naive read cannot produce a number the data does not support.

kind: "bare" — 4 or more completed sales

"headline": {
 "kind": "bare",
 "confidence": "HIGH",
 "window": "7d",
 "basisWindow": "30d",
 "estimator": "decay-median-3d",
 "method": "recency-weighted median over the trailing 30-day kept-sale sample",
 "medianUsd": 35,
 "currency": "USD",
 "salesInWindow": 15,
 "salesInBasis": 22
}

Safe to display as a plain number. confidence is HIGH (10+ sales) or MEDIUM (4–9). Attach sample claims to basisWindow and salesInBasis, never to the legacy selector fields window and salesInWindow.

kind: "thin" — 1 to 3 completed sales

"headline": {
 "kind": "thin",
 "confidence": "LOW",
 "window": "90d",
 "basisWindow": "90d",
 "estimator": "window-median",
 "method": "plain median of the completed sales in the 90d window",
 "medianUsd": 39.9,
 "currency": "USD",
 "salesInWindow": 1,
 "salesInBasis": 1,
 "qualifier": "Thin sample (1-3 completed sales) — a rough number, not a settled market price. Do not display this figure without this qualifier."
}

A number is present, and it arrives with a qualifier that is not optional. One sale is a data point, not a price. Display them together or display neither.

Headline estimator and sample basis

window is the schema-v1 adaptive selector and keeps that meaning for backward compatibility. It does not necessarily name the sample behind medianUsd. The additive fields are the price contract:

FieldMeaning
basisWindowWindow whose kept-sale sample owns the headline's n, range, dates and receipts
estimatordecay-median-3d or window-median
methodReader-facing description of that estimator
salesInBasisCompleted sales in basisWindow; this is the count to display beside medianUsd
windowLegacy adaptive selector; preserved, never repurposed
salesInWindowLegacy count for window; preserved, but not necessarily the headline sample count

decay-median-3d is a recency-weighted median over the trailing 30-day kept pool, so basisWindow is 30d. window-median is the plain median of the selected window, so basisWindow === window.

On an older snapshot where provenance cannot be proved, basisWindow and estimator are null, method and salesInBasis are absent, and no receipt count is presented as the headline sample. The price tier still travels with the number; do not invent a method or sample from window.

kind: "none" — no verified completed sale

"headline": {
 "kind": "none",
 "confidence": "NONE",
 "note": "No verified completed sales yet — tracking live."
}

There is no medianUsd key at all. Not 0, not null — absent. A naive card.headline.medianUsd read gets undefined, which is hard to accidentally render as a price.

26 of 280 printings are in this state. Show them as "no verified sales yet".

Today's census

TierconfidencePrintings
bareHIGH172
bareMEDIUM46
thinLOW36
noneNONE26

Half the catalogue has never sold in a marketplace this project watches. That number is published rather than filled in.

/api/v1/cards.json

The envelope, plus count and a cards array. About 342 KB, one request, everything.

{
 "cardId": "pal-BP01-001-OSR",
 "code": "BP01-001",
 "set": "BP01",
 "number": "001",
 "variant": "OSR",
 "rarity": "OSR",
 "rarityTier": "chase",
 "names": {
  "en": "Jormuntide Ignis – Savage Lava Dragon",
  "jp": "荒ぶる溶岩竜 – アグニドラ"
 },
 "pageUrl": "https://palworldprices.com/cards/bp01/001-jormuntide-ignis-savage-lava-dragon-osr/",
 "headline": { … },
 "asks": { "n": 24, "lowUsd": 25 }
}
FieldNotes
cardIdStable key and the filename for the detail endpoint
codePrinted code. Shared by every printing of the card
set / numberThe two halves of code
variantOSR, SSP, SP, RR, … or null for the base printing
rarityPrinted rarity. Can be a dual form such as "RR/SSP"
rarityTierchase, super-rare, rare, uncommon, common, promo
names.en / names.jpEither can be null
pageUrlThe human page: price history and available source-backed sale evidence
asksPresent only when the printing has a fresh listing. See Asks

Printings are not interchangeable

BP01-001 exists four times, and the four price separately:

cardIdPrintingPrice
pal-BP01-001base (RR)~$39.90 thin
pal-BP01-001-RRRR$9.99
pal-BP01-001-OSROSR$35.00
pal-BP01-001-SSPSSP$475.00

Note that the base printing and the -RR printing both carry printed rarity RR and differ by 4×. Key on cardId, never on code alone, and never average printings together.

/api/v1/cards/{cardId}.json

The card's identity and headline again, plus what sits underneath them.

curl -s https://palworldprices.com/api/v1/cards/pal-BP01-001-OSR.json
{
 "source": "Palworld Prices",
 …
 "cardId": "pal-BP01-001-OSR",
 "code": "BP01-001",
 "names": { "en": "Jormuntide Ignis – Savage Lava Dragon", "jp": "荒ぶる溶岩竜 – アグニドラ" },
 "pageUrl": "https://palworldprices.com/cards/bp01/001-jormuntide-ignis-savage-lava-dragon-osr/",
 "headline": {
  "kind": "bare",
  "confidence": "HIGH",
  "window": "7d",
  "basisWindow": "30d",
  "estimator": "decay-median-3d",
  "method": "recency-weighted median over the trailing 30-day kept-sale sample",
  "medianUsd": 35,
  "currency": "USD",
  "salesInWindow": 15,
  "salesInBasis": 22
 },
 "market": "Raw · US completed sales",
 "confidence": "HIGH",
 "suspect": false,
 "windows": {
  "7d": {
   "sales": 15,
   "salesSettled": 15,
   "medianUsd": 35,
   "lowUsd": 20.5,
   "highUsd": 49.99,
   "iqrUsd": [29.99, 42.95],
   "lastSoldAt": "2026-08-01T12:00:00.000Z",
   "datePrecision": "exact",
   "suspect": false
  },
  "30d": { … },
  "90d": { … }
 },
 "receipts": {
  "count": 22,
  "basisWindow": "30d",
  "note": "Source-backed receipt IDs are listed on the card page; public listing links appear when available."
 },
 "asks": { … }
}
FieldNotes
marketWhich market the headline came from, in words. null when unpriced
confidenceTier of the underlying bucket, HIGH … NONE
suspectThe build flagged something odd about the sample
windows7d / 30d / 90d. A window with no sales is absent, not zeroed
receipts.countNumber of fully resolved receipts in the estimator basis; receipts is null rather than overstating an incomplete legacy export
asksCurrent asking prices. Not a price. Absent when there is no fresh listing

Window fields

FieldNotes
salesSales in the window
salesSettledHow many are fully settled
medianUsdThat window's plain median. Use with the window's own sales count; it may differ from the headline estimator
lowUsd / highUsdRange
iqrUsd[p25, p75], or null
lastSoldAtISO timestamp of the most recent sale
datePrecisionexact when the marketplace stated a sale time, observed when it did not and this is when the sale was confirmed
suspectPer-window flag

datePrecision: "observed" means the marketplace did not publish a sale timestamp. Treat those dates as "no later than".

When a card has no verified sale, windows, receipts and market are all null and confidence is "NONE".

Asks — what sellers want

Everything above is a completed sale. asks is the one block on this site that is not, and it lives in its own top-level key for exactly that reason.

"asks": {
 "n": 24,
 "market": "US marketplace listings",
 "lowUsd": 25,
 "highUsd": 99.99,
 "medianAskUsd": 40,
 "windowHours": 72,
 "basis": "first-seen",
 "asOf": "2026-08-02T20:58:09.650Z",
 "listings": [
  { "priceUsd": 25, "marketplace": "eBay", "url": "https://www.ebay.com/itm/398238804180", "firstSeenAt": "2026-08-01T17:18:10.382Z" }
 ],
 "spreadPct": -28.6,
 "soldMedianUsd": 35,
 "soldWindow": "7d",
 "soldBasisWindow": "30d",
 "soldEstimator": "decay-median-3d",
 "soldMethod": "recency-weighted median over the trailing 30-day kept-sale sample",
 "note": "ASKING prices … NOT sales …"
}
FieldNotes
nHow many fresh listings were counted
lowUsdThe cheapest one. Always present — it is one verifiable fact about one listing
medianAskUsdnull below 4 asks. A median over one to three listings describes nothing
windowHoursThe freshness window, currently 72
basis"first-seen". Read the next paragraph before you word anything
listingsUp to the five cheapest, ascending, each with a clickable URL
spreadPctOptional. The cheapest ask against the completed-sale estimate of the same market
soldWindowLegacy selector window; preserved, not the estimator sample
soldBasisWindowSample basis behind soldMedianUsd
soldEstimator / soldMethodMachine- and reader-facing estimator provenance

basis: "first-seen" is load-bearing. A listing is recorded once, when it is first seen, and is not re-checked afterwards. Two departures are subtracted (we later captured the sale, or a recheck labelled it sold); everything else is bounded by the window alone. So this data supports *"24 listed from $25, seen in the last 3 days"*. It does not support "24 available now", "in stock", or "verified active" — and an ask is never a sale, so it must never fill an unpriced card, enter a price average, or be displayed as a price.

spreadPct appears only when the card has a bare-renderable sold estimate in the same market and at least two fresh asks. Missing means missing: no zero, no "flat", and do not re-derive it from the other fields. It is not a JP/US arbitrage spread — that does not exist on this site.

Missing cards

Ask for a cardId that does not exist and you get HTTP 404 with the site's HTML not-found page — served under this API's JSON content type, because the header rule matches the requested path and there is no application server here to notice the difference. The status is honest; the body is not JSON. So do not rely on the content type, and do not assume an error body you can parse. The reliable check is one line:

const r = await fetch(`https://palworldprices.com/api/v1/cards/${id}.json`);
const card = await r.json().catch(() => null);
if (!card || card.cardId !== id) throw new Error(`unknown cardId: ${id}`);

Parse, then check cardId matches what you asked for. That guard is correct whatever the host returns — today's 404 page, a future JSON error, or a stale edge copy — and it also catches the case where a card is retired between builds.

For a canonical example of the error shape, /api/v1/404.json is a real document describing exactly this convention.

Take valid cardIds from cards.json rather than constructing them.

Feeds

All three are RSS 2.0, application/rss+xml.

/feeds/latest-sales.xml — the newest individual completed sales. Each item links both the card page and the original listing.

<item>
  <title>$83.21 — Lyleen – Blessing of the Goddess (BP01-049 SSP)</title>
  <link>https://palworldprices.com/cards/bp01/049-lyleen-blessing-of-the-goddess-ssp/</link>
  <guid isPermaLink="false">pwp-sale-mercarijp-m49105711564</guid>
  <pubDate>Mon, 03 Aug 2026 01:33:10 GMT</pubDate>
  <description>Last verified 2026-08-03 (the marketplace did not state the sale time, so this is when we confirmed it) on eBay US for $83.21. This is ONE completed sale, not a price — a single data point. …</description>
</item>

Each item is one sale. The wording says so, because a feed of individual sales is the easiest thing in the world to misread as a feed of prices.

/feeds/movers.xml — cards whose 7-day median moved against their 30-day median, published only when both windows rest on enough dated, deduplicated sales.

<title>▼ 5.9% — Jormuntide – Surging Sea Serpent (BP01-027 OSR) at $40.00</title>
<description>7-day median $40.00 across 13 completed sales, against a 30-day median of $42.50 across 14. That is down 5.9%. A move is published only when both windows rest on enough dated, deduplicated sales to back it. …</description>

When nothing qualifies, the feed is not published at all. An empty movers feed would be a feed that says nothing; a missing one says the same thing honestly.

/feeds/changelog.xml — what changed on the site, in plain language. The feed is the changelog's only public form. Subscribe to it if you depend on this data and want to know when the way a number is made changes.

guids are stable and namespaced (pwp-sale-…, pwp-mover-…), so readers deduplicate correctly across rebuilds.

/llms.txt

Plain text, for assistants: what this source is, how it differs from asking-price sites, the rules for quoting a number, the key URLs, and what is deliberately absent.

curl -s https://palworldprices.com/llms.txt

Freshness and caching

Rebuilt daily. Every payload carries asOf and stale; nothing here promises to be current.

Cache-Control: public, max-age=0, s-maxage=3600, stale-while-revalidate=86400
ETag: "d8b7c81c6543e3fe10d7a480495bd0e5"

max-age=0 keeps browsers honest — a price must never be browser-stale — while the edge answers instantly and revalidates behind the request. Send If-None-Match with the ETag and you will get 304s between builds.

There is no key to revoke and no per-user quota — see User-Agent policy for the one enforcement rule that exists, and note that reasonable automated rate limits may apply to any client that requests far past what a real integration needs.

User-Agent policy

Send a descriptive User-Agent — something that names your app, e.g. MyApp/1.0 (+https://example.com). It costs nothing and it is the only thing that lets a maintainer tell "someone's real integration" from "anonymous bulk scrape" when this API gets busy.

On the bulk-ish surface (/api/v1/* and the legacy /search-index.json), requests with no User-Agent at all, or an unmodified HTTP-library default (curl/8.4.0, python-requests/2.31.0, Go-http-client/1.1, and similar), get 403 with a short plain-text body pointing back here. Anything else — a browser, a custom app, this site's own client-side search, the MCP server, a curl -A "MyApp/1.0" — passes through untouched. This is a politeness gate, not a paywall: there is still no key and no account, and every field this API publishes today keeps publishing.

Stability

Additive changes only within v1. Fields may be added; existing fields will not be removed or repurposed. A breaking change gets a new version prefix.

Write your parser to ignore unknown fields.

Attribution

Free with attribution, including commercial use. Publish this line verbatim wherever you display these numbers:

Data: Palworld Prices (palworldprices.com) — completed-sale prices with confidence tiers

Link back to the card's pageUrl so a reader can inspect the available source-backed sale evidence and public listing links. Do not imply endorsement.

Card images are not part of this API, and there is no bulk image download. Card art is hosted under a publisher fan-content policy that does not extend to redistribution. Link to the card page instead.

This site is non-commercial: no ads, no affiliate links.

Licence

What you may and may not do with this data, in plain language, not legalese:

If you are unsure whether a use case fits, ask: contact@palworldprices.com. That is a faster answer than guessing wrong.

The one rule

If you take one thing from this page: never publish a thin median without its qualifier, never publish a number for a none card, and never publish an ask as a price. Everything else here is a detail.

Recipes

Five most valuable confidently-priced printings, from the current build:

curl -s https://palworldprices.com/api/v1/cards.json | node -e "
let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{
const {cards}=JSON.parse(s);
cards.filter(c=>c.headline.kind==='bare')
     .sort((a,b)=>b.headline.medianUsd-a.headline.medianUsd)
     .slice(0,5)
     .forEach(c=>console.log(c.headline.medianUsd.toFixed(2).padStart(8), c.code, c.variant||'', '—', c.names.en));
})"
 2500.00 ESOUL-002  — Soul
  785.00 BP01-025 SSP — Chillet – Dragon Whisperer
  750.00 TD02-012 TSP — Astegon – Aegis Wyvern of Death
  700.00 ESOUL-008  — Soul
  450.00 TD01-001 TSP — Grizzbolt – Rumbling Tank

Render a price the honest way, in any language — the branch is always the same three cases:

function priceLabel(card) {
  const h = card.headline;
  if (h.kind === 'withheld') return 'Sold estimate temporarily unavailable';
  if (h.kind === 'none') return 'No verified sales yet';
  if (h.kind === 'thin') return `~$${h.medianUsd.toFixed(2)} (thin sample)`;
  return `$${h.medianUsd.toFixed(2)}`;
}

Four branches. Write them once and the qualifier or temporary-withhold state can never go missing.

What is not here

Graded (PSA) values, pack expected-value, and JP/US arbitrage spreads. They do not exist on this site — absent rather than estimated.

Individual sale rows are not in the JSON either. When receipts is non-null, receipts.count gives the complete source-backed estimator-basis count. The card page lists the available source evidence and public listing links when supplied.

Questions

Something wrong, something missing, or a use case this does not cover: contact@palworldprices.com.

Prices are informational, from public completed-sale data. Not financial advice. Method: /methodology/. What we refuse to print and why: /accuracy/. Prices inside an assistant instead: the MCP server.