# Liga Pokemon Card Prices Scraper (`dami_studio/ligapokemon-card-prices-scraper`) Actor

Pokemon TCG card prices from Brazil's Liga Pokemon marketplace. Get a card's low, average and high price in BRL alongside its set and collector number, or pull one seller's whole shelf with condition, language, stock and price. No account needed.

- **URL**: https://apify.com/dami\_studio/ligapokemon-card-prices-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 card price returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Liga Pokemon Card Prices Scraper

Pulls Pokemon TCG card prices off **Liga Pokemon** (ligapokemon.com.br), the Brazilian marketplace
where most Pokemon singles in Brazil actually change hands. Two things come back: the price band a
card is trading at across the whole marketplace, and, if you ask for a shop, every single listing
that seller has on their shelf, with its condition, language, stock and price.

Everything is in **Brazilian reais**, because that is the currency the marketplace quotes.

No account, no cookies, no API key. You give it card names or a shop id and it gives you rows.

***

### What you get

#### Card rows: what a card is worth right now

One row per card, carrying the identity *and* the price band:

```json
{
  "rowType": "card",
  "cardId": "796",
  "cardName": "Charizard ex",
  "collectorNumber": "006",
  "setSize": "165",
  "collectorCode": "006/165",
  "editionId": "411",
  "editionName": "151",
  "priceMinBrl": 79.9,
  "priceAvgBrl": 106.5,
  "priceMaxBrl": 195,
  "priceChangeTodayBrl": -0.99,
  "currency": "BRL",
  "identityMatch": "exact",
  "searchTerm": "Charizard ex (006/165)",
  "imageUrl": "https://repositorio.sbrauble.com/arquivos/in/pokemon_bkp/cd/411/796s_006.jpg",
  "url": "https://www.ligapokemon.com.br/?view=cards/card&card=Charizard+ex%20(006/165)&num=006",
  "scrapedAt": "2026-09-20T01:29:35.947Z"
}
```

`priceMinBrl` is the cheapest copy anyone is selling, `priceMaxBrl` the dearest, `priceAvgBrl` the
marketplace's own average, and `priceChangeTodayBrl` how much that average moved today. All four are
the marketplace's numbers, not ours.

#### Listing rows: one seller's whole shelf

Put a shop id in `sellerStores` and you get a row per listing instead:

```json
{
  "rowType": "listing",
  "listingId": "30468606",
  "cardId": "7492",
  "cardName": "Billy & O'Nare",
  "cardNamePt": "Abílio e Onara",
  "collectorNumber": "142",
  "setSize": "159",
  "setName": "Journey Together",
  "setNamePt": "Amigos de Jornada",
  "setCode": "JTG",
  "rarity": "Comum",
  "rarityCode": "C",
  "conditionCode": "SP",
  "condition": "Slightly Played",
  "languageCode": "EN",
  "language": "English",
  "extrasCode": "3",
  "isGraded": false,
  "grading": null,
  "stock": 7,
  "priceBrl": 0.49,
  "discountPct": 0,
  "currency": "BRL",
  "sellerStoreId": "97457",
  "sellerName": "Admir das Garde",
  "sellerRating": 5,
  "sellerRatingCountAtLeast": 250,
  "sellerVerified": true,
  "url": "https://www.ligapokemon.com.br/?view=cards/card&card=Billy%20%26%20O'Nare%20(142%2F159)&num=142"
}
```

Conditions come back as the trade's own codes (`M`, `NM`, `SP`, `MP`, `HP`, `D`) with the plain
English label beside them. Languages the same way: `PT`, `EN`, `ES`, `JP` and the rest, each with its
name spelled out.

***

### Input

```json
{
  "searchTerms": [
    "Charizard ex (006/165)",
    "Pikachu ex (057/191)",
    "Gardevoir ex"
  ],
  "maxItems": 100,
  "strictMatch": true
}
```

| Field | What it does |
|---|---|
| `searchTerms` | One card per line. With a collector number you get that exact printing; without one you get every printing the marketplace lists under that name. |
| `startUrls` | Paste card pages or a shop link straight from the site. |
| `sellerStores` | A shop id, or the shop link it came from. Returns that seller's entire Pokemon shelf. |
| `includeSellerDetails` | Adds shop name, rating and verified badge to listing rows. One extra request per shop. |
| `maxItems` | Hard cap on rows. You are charged per row, so this is your spend control. |
| `strictMatch` | Leave this on. See below: it is the difference between a price for your card and a price for somebody else's. |

A run takes up to 400 card lookups and 50 shops. Past that it tells you what it skipped instead of
quietly doing less.

***

### The thing worth knowing before you use this

**Liga's search will sometimes answer with the wrong card, and it looks completely normal when it
does.**

Ask it for `Chien-Pao ex (061/195)` and it comes back with **Hypno 061/195**, from a different set.
Same collector number, entirely different card, real price attached. Nothing about the response
looks broken. If you took that number as the price of a Chien-Pao ex you would be wrong by a wide
margin, and you would never know.

So the actor checks the name *and* the number on every row against what you asked for. When they do
not line up, you get a free note instead of a price:

```json
{
  "_diagnostic": true,
  "charged": false,
  "errorCode": "NO_EXACT_MATCH",
  "searchTerm": "Chien-Pao ex (061/195)",
  "error": "The marketplace found cards with that collector number but not that card name.",
  "returnedInstead": ["Hypno (061/195) in Silver Tempest"],
  "hint": "Liga lists many cards under their Brazilian Portuguese names, so an English name plus a number can miss. Search the name on its own, or turn \"strictMatch\" off to take what the marketplace returned. Nothing was charged."
}
```

That is `strictMatch` doing its job. Turn it off and you get whatever the search returned, each row
tagged `identityMatch: "number-only"` so you can still tell the difference.

**The flip side: Liga is a Brazilian site and a lot of cards are listed under Portuguese names.**
Rare Candy is *Doce Raro*. Boss's Orders is *Ordem da Chefia*. Roaring Moon ex is *Lua Estrondo ex*.
If you pin an English name plus a number and it does not match, that is usually why. Search the
name on its own and you will find it. The one case handled automatically is VSTAR, which Liga writes
as V-ASTRO.

***

### What this does not do

Worth reading before you buy, because these are real gaps and not one of them is going to surprise
you later.

- **No sold-price history.** Liga publishes past sales only to signed-in members, so a signed-out
  scraper cannot see them and this one does not pretend to. You get today's asking prices and today's
  movement.
- **No cross-seller listing view for one card.** You can get the price *band* for any card, and you
  can get every listing from a shop you name, but "show me all 40 sellers offering this one card"
  sits behind a part of the site a signed-out visitor cannot read.
- **`extrasCode` is a raw code, not a label.** It marks foils, promos and similar variants. The
  marketplace does not publish the code table for Pokemon anywhere a signed-out visitor can read it,
  so the number is passed through as-is rather than given a made-up name.
- **`sellerCity` is usually null.** The shop panel does not always carry it.
- **Some runs will not get through.** The marketplace admits a minority of network addresses at any
  moment. The actor tries up to 30 before giving up, and around one run in fifty still finds none. It
  says so in a free row and charges you nothing for the rows it never got. Re-running usually works;
  supplying your own Brazilian proxy makes it reliable.
- **Promo printings have odd collector numbers**: `021/∞`, `TG01/TG30`, `065C/151`. They come
  through as written. They are not errors.

***

### Billing

You are charged **per row returned**, and only for rows that carry a complete card identity. The
per-row rate is on this page.

Free, always:

- the sample row you get when you run it with nothing filled in
- every `_diagnostic` row: no route, no results, wrong card, unknown shop, time limit
- rows dropped because the identity could not be read

So a run that finds nothing costs you the run fee and nothing else. `maxItems` is a hard ceiling, not
a target: if the searches return fewer rows than that, you pay for the rows you got.

***

### Speed and scale, from real runs

| Run | What it did | Rows | Time |
|---|---|---|---|
| 12 pinned card lookups | one printing each | 11 cards + 1 free note | 15 s |
| one seller's shelf | condition, language, stock, price | 120 listings | 25 s |
| empty input | nothing to look up | 1 free sample | 2 s |

Card lookups cost roughly one request each, so they scale with the number of cards you ask for. A
seller's shelf comes back in pages of a few dozen, so shops are much faster per row: 120 listings
took five requests.

The actor paces itself deliberately. Liga rate-limits hard, and a run that sprints gets cut off
part-way with half your rows missing. Slower and complete beats fast and truncated.

***

### FAQ

**Is there a Liga Pokemon API?**
Not a public, documented one. This actor reads the same public marketplace data the site itself
serves, and hands it back as clean JSON rows.

**Can I scrape Liga Pokemon prices without an account?**
Yes, for current asking prices, which is what this does. Past sale prices are members-only on Liga
and are not available to any signed-out tool, this one included.

**What currency are the prices in?**
Brazilian reais (BRL). The marketplace quotes in BRL and the numbers are passed through untouched.

**How do I find a seller's shop id?**
Open the shop on Liga and take the number after `store=` in the address bar. Or just paste the whole
link into `sellerStores` and it will pull the id out.

**What do NM, SP, MP mean?**
Card condition, near-mint down to damaged: `M` mint, `NM` near mint, `SP` slightly played, `MP`
moderately played, `HP` heavily played, `D` damaged. Every listing row carries both the code and the
spelled-out label.

**Why did my search return nothing?**
Usually the Portuguese name. Liga lists Rare Candy as *Doce Raro*. Drop the collector number and
search the card name on its own first. That almost always finds it.

**Can I track a card's price over time?**
Run it on a schedule and keep the rows. Each row carries `scrapedAt` and `priceChangeTodayBrl`, so a
daily run builds the history. The actor cannot hand you history it never saw.

**Does it work for sealed product, not just singles?**
Cards are what it returns today. Sealed boxes and accessories live in a different part of the
marketplace and are not covered.

# Actor input Schema

## `searchTerms` (type: `array`):

One card per line. Add the collector number to pin a single printing - "Charizard ex (006/165)" - or give the name on its own, "Charizard", to get every printing the marketplace lists under it. Liga is a Brazilian site and lists a lot of cards under their Portuguese names, so if an English name plus a number finds nothing, try the name by itself.

## `startUrls` (type: `array`):

Optional. Paste card pages (https://www.ligapokemon.com.br/?view=cards/card\&card=Charizard+ex%20(006/165)) or a seller's shop link, the one carrying \&store=. Works alongside the fields above.

## `sellerStores` (type: `array`):

Optional. A shop id, or the shop link it came from, returns that seller's whole Pokemon shelf: every listing with its condition, language, stock and price in BRL. The id is the number after "store=" in a shop's address.

## `includeSellerDetails` (type: `boolean`):

Adds the shop's name, star rating and verified badge to every listing row. One extra request per shop. Turn it off if you only care about prices.

## `maxItems` (type: `integer`):

Hard cap on the rows this run returns, across every search and shop. You are charged per row returned.

## `strictMatch` (type: `boolean`):

On by default, and worth leaving on. When you pin a collector number, the marketplace's search sometimes answers with a different card that happens to share that number - a search for "Chien-Pao ex (061/195)" comes back with Hypno 061/195 from another set. With this on, those are reported as an uncharged note instead of being returned and billed as a price for your card.

## `proxyConfiguration` (type: `object`):

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "searchTerms": [
    "Charizard ex (006/165)"
  ],
  "startUrls": [],
  "sellerStores": [],
  "includeSellerDetails": true,
  "maxItems": 100,
  "strictMatch": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per card (name, set, collector number, low/average/high price in BRL and the day's change) or per seller listing (condition, language, stock, price in BRL, shop name and rating). Empty input, an unreadable card identity or a search that came back with the wrong card writes an uncharged sample or diagnostic row instead.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "Charizard ex (006/165)"
    ],
    "startUrls": [],
    "sellerStores": [],
    "includeSellerDetails": true,
    "maxItems": 100,
    "strictMatch": true,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/ligapokemon-card-prices-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": ["Charizard ex (006/165)"],
    "startUrls": [],
    "sellerStores": [],
    "includeSellerDetails": True,
    "maxItems": 100,
    "strictMatch": True,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/ligapokemon-card-prices-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "Charizard ex (006/165)"
  ],
  "startUrls": [],
  "sellerStores": [],
  "includeSellerDetails": true,
  "maxItems": 100,
  "strictMatch": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/ligapokemon-card-prices-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/ligapokemon-card-prices-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/w4Z8fK2Of3EPVQPuq/builds/cA6tyLJH46IMWYkAh/openapi.json
