BerryWallet vs one-piece-api.com: One Piece TCG API Comparison

🔵 IN THIS GUIDE: This isn't a spec sheet built on marketing claims. It's a small Node script that runs the same One Piece TCG queries against BerryWallet and one-piece-api.com and measures set coverage, price freshness, rate-limit behavior and uptime, so you can compare them on your own traffic before you commit.

Quick Answer: If your app needs live market prices, use BerryWallet. Each price row puts TCGPlayer (low, mid, high, market) next to CardMarket (avg, low, trend, 7-day and 30-day averages) for the same card and variant, and you can pull a whole set at 200 rows per page. We're not publishing rate-limit or uptime figures for either service in this article, because a number we can't verify is worse than no number. Instead, the script below measures both on your own key and traffic in about ten minutes. Run it before you choose, and keep it running as a cron job after you do.

Any developer who searches "one piece card game api" ends up comparing the same two or three options, and most comparison pages online are written by one of the vendors. This one is too: we build BerryWallet. That's why this article doesn't ask you to trust our numbers. It gives you a tool that produces your own.

The comparison covers the four things that actually break a collection app in production. Set coverage means whether a set's full card list and every variant come back. Price freshness means whether prices are recent enough to show a buyer. Rate limits means what happens when you push traffic. Uptime means whether the API is available when your users are. A static feature table can't answer any of these reliably. Measuring can.

We'll also be upfront about where BerryWallet is thinner. The One Piece API lives under /op and has fewer endpoints than its Pokémon sibling. There's no trending-sets or set-statistics endpoint for One Piece yet. If your product depends on those, factor that in.

Key Takeaways

  • 🧪 Measure, don't trust: rate limits and uptime depend on your plan, region and traffic pattern, so run the script in this guide on both APIs with your real keys.
  • 💱 Two marketplaces per row: a BerryWallet price row returns TCGPlayer and CardMarket together, so EU and US pricing never need to be joined by hand.
  • 📉 Trend data is built in: CardMarket avg7 and avg30 come in every row, which lets you show "is this card moving?" without storing your own history on day one.
  • 📄 Always paginate: request limit=200 and loop until you reach total. An unpaginated call returns only the first page and doesn't tell you it was truncated.
  • 🧩 Coverage means variants: compare card counts per card_number + variant, not just card names. Alternate arts and parallels are where most APIs lose data.
  • ⏱ Handle 429 before launch: read Retry-After when the API sends it, and back off exponentially when it doesn't. The script shows both.
  • 🗂 Cache sets, refresh prices: card lists almost never change after release, but prices do, so they deserve different TTLs.
  • 🔍 Know BerryWallet's gaps: the /op API doesn't expose trending or statistics endpoints yet. It covers card lists and prices.


What You Will Build

You'll build op-api-bench.mjs, a dependency-free Node script that treats each One Piece TCG API as a config object and runs the same four checks against every one:

CheckWhat it measuresWhy it matters
CoverageCards returned for a set vs. the total the API declares, counted per number + variantMissing parallels and alternate arts are the most common data hole
Price completenessShare of cards that have a TCGPlayer and/or CardMarket priceA card with no price is a blank cell in your UI
FreshnessAge of the most recent and the median updated_at across price rowsStale prices make an app look wrong
ReliabilityStatus codes, latency and rate-limit headers, logged every runOver a week of cron runs, this becomes your uptime record

The output is one JSON line per API per run. Append it to a file, and a week later you'll have an uptime and latency history for both services that you collected yourself.

The script is written generically on purpose. BerryWallet's config uses its real endpoints. For one-piece-api.com, you fill in the base URL, auth header and paths from their current documentation. We don't hardcode them, because an endpoint that changes after publication would quietly make the comparison unfair.

Prerequisites

  • Node.js 22+. The script uses the built-in fetch and AbortSignal.timeout and needs no npm packages.
  • A BerryWallet API key (see Step 1).
  • Access to one-piece-api.com, if it requires a key, plus their docs open in a tab so you can fill in the config.
  • A set code to test with. We use OP01 because every One Piece API should cover it fully. Run a recent set too, since that's where coverage gaps show up first. If you need a refresher on what you're counting, our One Piece TCG rarity guide explains every symbol from C to SEC, including alternate arts.

Basic familiarity with REST and JSON is enough. If you've never called a TCG price API before, start with how to fetch real-time OPCG card prices and come back.

Step 1: Get Your API Key

BerryWallet runs on the same API infrastructure as PokéWallet. One account and one key give you both the Pokémon endpoints and the One Piece endpoints under /op. Sign up, open the developer section of your dashboard and generate a key. The plans and what each tier includes are covered in our post on the API leaving beta. Check your dashboard for the current per-plan limits rather than relying on a blog post, including this one.

Keep the key out of source control:

export BERRYWALLET_API_KEY="your-key-here"
export OPAPI_KEY="competitor-key-if-required"

Every request authenticates with one header:

X-API-Key: <your key>

You don't need OAuth, token refresh or signed requests. That's a real DX advantage for scripts and serverless functions. Still, confirm how the competitor authenticates before you assume it's equivalent.

Step 2: The First Request

Start with the call that matters most for a price app: every price row for one set.

curl -s "https://api.pokewallet.io/op/prices?set_code=OP01&page=1&limit=200" \
  -H "X-API-Key: $BERRYWALLET_API_KEY" \
  -H "Accept: application/json"

Two details are easy to get wrong:

  1. The set goes in the query string (/op/prices?set_code=OP01), not in the path. The card list is /op/sets/OP01.
  2. Results are paginated. The list is in data, and the full size is in total. Without limit, you get only the first page, and the response doesn't warn you. Always pass limit=200 and loop.

Each row in data looks like this (shape only, values omitted):

{
  "card_number": "OP01-...",
  "name": "...",
  "variant": "...",
  "updated_at": "...",
  "tcgplayer": {
    "low_price": 0, "mid_price": 0, "high_price": 0, "market_price": 0
  },
  "cardmarket": {
    "avg": 0, "low": 0, "trend": 0, "avg7": 0, "avg30": 0
  }
}

This is BerryWallet's main advantage over a catalog-only API. Both marketplaces are in one row, keyed by card number and variant. US and EU prices differ enough that an app showing only one misleads half its users. Our TCGPlayer vs CardMarket vs eBay breakdown explains why the gap exists. The CardMarket avg7 and avg30 fields also let you draw a trend arrow without any stored history.

Now send the equivalent request to one-piece-api.com and compare the shapes. Look for four things: whether prices exist at all, which marketplace they come from, whether there's a per-row timestamp, and whether variants are separate rows or flattened into one card.

Step 3: Building It Out

Here's the full script. Each API is a config object, and everything else is shared.

// op-api-bench.mjs — node op-api-bench.mjs OP01 >> bench.jsonl
const SET = process.argv[2] ?? 'OP01'
const TIMEOUT_MS = 20_000

const APIS = [
  {
    name: 'berrywallet',
    base: 'https://api.pokewallet.io',
    headers: { 'X-API-Key': process.env.BERRYWALLET_API_KEY },
    cards: (set, page) => `/op/sets/${set}?page=${page}&limit=200`,
    prices: (set, page) => `/op/prices?set_code=${set}&page=${page}&limit=200`,
    list: (body) => body.data,
    total: (body) => body.total ?? body.pagination?.total,
    key: (row) => `${row.card_number}|${row.sub_type_name ?? row.variant ?? ''}`,
    hasPrice: (row) => row.tcgplayer?.market_price != null || row.cardmarket?.trend != null,
    updatedAt: (row) => row.updated_at,
  },
  {
    name: 'one-piece-api',
    // Fill these in from one-piece-api.com's current docs.
    base: process.env.OPAPI_BASE,
    headers: process.env.OPAPI_KEY ? { Authorization: `Bearer ${process.env.OPAPI_KEY}` } : {},
    cards: (set, page) => `/TODO/cards?set=${set}&page=${page}`,
    prices: null, // set to a function if they expose prices
    list: (body) => body.data ?? body.cards ?? body,
    total: (body) => body.total,
    key: (row) => `${row.number ?? row.id}|${row.variant ?? ''}`,
    hasPrice: (row) => row.price != null,
    updatedAt: (row) => row.updated_at,
  },
]

async function get(api, path) {
  const started = performance.now()
  const res = await fetch(api.base + path, {
    headers: { Accept: 'application/json', ...api.headers },
    signal: AbortSignal.timeout(TIMEOUT_MS),
  })
  const ms = Math.round(performance.now() - started)
  const limits = Object.fromEntries(
    [...res.headers].filter(([h]) => /ratelimit|retry-after/i.test(h))
  )
  const body = res.ok ? await res.json() : null
  return { status: res.status, ms, limits, body }
}

async function fetchAll(api, pathFn, log) {
  const rows = []
  let declared = null
  for (let page = 1; page <= 20; page++) {
    const r = await get(api, pathFn(SET, page))
    log.push({ status: r.status, ms: r.ms, limits: r.limits })
    if (!r.body) break
    const batch = api.list(r.body) ?? []
    declared ??= api.total(r.body) ?? null
    rows.push(...batch)
    if (!batch.length || (declared != null && rows.length >= declared)) break
  }
  return { rows, declared }
}

function median(xs) {
  const s = [...xs].sort((a, b) => a - b)
  return s.length ? s[Math.floor(s.length / 2)] : null
}

async function bench(api) {
  const log = []
  const out = { api: api.name, set: SET, at: new Date().toISOString() }
  try {
    const cards = await fetchAll(api, api.cards, log)
    out.cards = { returned: cards.rows.length, declared: cards.declared,
                  unique: new Set(cards.rows.map(api.key)).size }

    if (api.prices) {
      const prices = await fetchAll(api, api.prices, log)
      const priced = prices.rows.filter(api.hasPrice)
      const ages = prices.rows.map(api.updatedAt).filter(Boolean)
        .map((t) => (Date.now() - Date.parse(t)) / 3_600_000)
      out.prices = {
        rows: prices.rows.length,
        pricedShare: prices.rows.length ? +(priced.length / prices.rows.length).toFixed(3) : 0,
        freshestHours: ages.length ? +Math.min(...ages).toFixed(1) : null,
        medianAgeHours: ages.length ? +median(ages).toFixed(1) : null,
      }
    } else {
      out.prices = 'no price endpoint configured'
    }
  } catch (err) {
    out.error = err.name === 'TimeoutError' ? 'timeout' : err.message
  }
  out.requests = log.length
  out.errors = log.filter((l) => l.status >= 400).map((l) => l.status)
  out.medianMs = median(log.map((l) => l.ms))
  out.limitHeaders = log.at(-1)?.limits ?? {}
  return out
}

for (const api of APIS.filter((a) => a.base)) {
  console.log(JSON.stringify(await bench(api)))
}

How to read the output:

  • cards.returned vs cards.declared: if they differ, the API is truncating or its pagination is broken. Either way, your app will be missing cards.
  • cards.unique: if this is lower than returned, variants are colliding under one key. That usually means alternate arts are being flattened into the base card.
  • prices.pricedShare: expect this to be below 1.0 on a fresh set. Cards nobody has listed yet have no price. This is a property of the market, not an API bug. Compare the two APIs on the same set on the same day.
  • freshestHours / medianAgeHours: the freshness verdict. Compare medians, not best cases.
  • limitHeaders: whatever rate-limit headers the API actually sends. This is the most honest source of an API's limits, more so than any pricing page.

Run it for a baseline:

node op-api-bench.mjs OP01 >> bench.jsonl
node op-api-bench.mjs OP06 >> bench.jsonl

Handling Rate Limits

We're not listing rate-limit numbers for either API. BerryWallet's depend on your plan, and your dashboard shows the current figure for your key. We haven't verified one-piece-api.com's limits ourselves, so we won't repeat them. What we can give you is the code that makes the exact number matter less.

Wrap get so that a 429 never becomes a failed page:

async function getWithBackoff(api, path, attempt = 0) {
  const r = await get(api, path)
  if (r.status !== 429 || attempt >= 5) return r
  const retryAfter = Number(r.limits['retry-after'])
  const waitMs = Number.isFinite(retryAfter)
    ? retryAfter * 1000
    : Math.min(30_000, 500 * 2 ** attempt) + Math.random() * 250
  await new Promise((resolve) => setTimeout(resolve, waitMs))
  return getWithBackoff(api, path, attempt + 1)
}

Three rules matter more than whatever the limit is:

  1. Pull whole sets, not single cards. Refreshing one set at 200 rows per page takes a handful of requests. Doing it card by card takes hundreds. This is the biggest lever you have on any API.
  2. Obey Retry-After when it's present. Otherwise use exponential backoff with jitter, so parallel workers don't all retry at the same moment.
  3. Log the limit headers on every run. The bench script already stores them. If an API tightens its limits, you'll see it in bench.jsonl before your users see errors.

To compare how each API behaves under load, run the script in a tight loop against a large set for a minute and count 429s and timeouts. Only do this within each provider's terms of service.

Going to Production

Turn the bench into your uptime monitor. Schedule it and keep the log:

# every 15 minutes, both APIs, one old set and one recent set
*/15 * * * * cd /opt/op-bench && node op-api-bench.mjs OP01 >> bench.jsonl && node op-api-bench.mjs OP06 >> bench.jsonl

After a week, the uptime for each API is the share of runs with no error and an empty errors array. This is measured from your own servers, which is the only uptime figure that affects your users. One caveat: a single vantage point can't tell a provider outage apart from a problem on your own network. If both APIs fail in the same run, suspect your side first.

Cache according to how the data changes:

DataChangesSuggested cache
Set card list (/op/sets/{code})Practically never after releaseDays; invalidate by hand when a set drops
Prices (/op/prices?set_code=)ContinuouslyMinutes to hours, depending on how "live" your UI claims to be

Store updated_at alongside every price you show, and display it. Collectors deciding whether to buy a card at a given price want to know how old that price is. Our One Piece TCG Europe guide shows how quickly CardMarket pricing moves on a hot release.

Design around the gaps. BerryWallet's /op surface today covers card lists and prices. It doesn't include trending sets, set statistics or top-movers analytics, which the Pokémon side has. If you need "biggest movers this week", compute it from the CardMarket avg7 vs. avg30 fields in the price rows. It's one subtraction per card, and the data is already in the response.

Our position: if your product shows prices, BerryWallet is the stronger base today because both marketplaces and the trend averages come in a single row. If you only need a card catalog without prices, the choice matters less, and the bench script will tell you which API gives you more complete variant coverage on the sets you care about. Either way, make the decision from bench.jsonl, not from a vendor blog. That includes this one.


Frequently Asked Questions

Is there a free One Piece card game API?

Yes. BerryWallet has a free tier on the same key that covers PokéWallet, and you can sign up without a credit card. Limits vary by plan, so check your dashboard for the current figures on your key. Before you rely on any free tier for a public app, run the bench script to see how it behaves under your actual traffic.

What's a good one-piece-api.com alternative with prices?

BerryWallet returns TCGPlayer (low, mid, high, market) and CardMarket (avg, low, trend, 7-day and 30-day averages) in the same row for each card and variant. If you need both US and EU pricing without merging two data sources yourself, that's the main reason to switch. Use the script in this guide to confirm coverage on the sets you need.

Does the BerryWallet API have CardMarket prices?

Yes. Every price row can include a cardmarket object with avg, low, trend, avg7 and avg30, next to a tcgplayer object. A card only has a price once it has real listings, so very new or very obscure cards can have an empty marketplace block.

Why does my API call only return part of a set?

The responses are paginated. Without limit, you get only the first page, and nothing in the response flags the truncation. Pass limit=200 and keep requesting pages until the rows you've collected reach total.

How often are One Piece TCG prices updated?

Every price row has an updated_at timestamp, and that's the number to trust, not a marketing claim. The bench script reports the freshest and the median age across a whole set. Run it on a recent set and an old set, because prices on a set with little activity naturally refresh less often.

What are the rate limits on the BerryWallet API?

They depend on your plan and are shown in your dashboard. We don't reprint them here because they can change. The bench script logs whatever rate-limit headers the API returns. With the backoff wrapper in this guide, an occasional 429 costs a short delay instead of a failed request.

Can I use one API key for Pokémon and One Piece?

Yes. BerryWallet and PokéWallet share the same API. Pokémon endpoints sit at the root and One Piece endpoints sit under /op, and the same X-API-Key header works for both. The One Piece side currently has fewer endpoints: card lists and prices, but no trending or statistics endpoints.

How do I check an API's uptime myself?

Schedule the bench script every 15 minutes and append its output to a file. After a week, uptime is the share of runs with no errors and no timeouts. Run it from the same region as your production servers, since that's the uptime your users actually experience.


💬 Price disclaimer: This article quotes no card prices, rate-limit figures or uptime percentages. Our data snapshot of September 26, 2026 contained no market figures for this piece, and we don't publish unverified numbers about any API, ours included. Endpoint and field names reflect the BerryWallet API as of that date. Details about one-piece-api.com should be checked against their current documentation. Nothing here is financial advice.


Track One Piece Card Game Prices Live with BerryWallet

The same price rows this script measures, with TCGPlayer and CardMarket together and 7- and 30-day trends, power the BerryWallet app. If you'd rather track your collection than build the tracker, it's already built.

  • 🔔 Price alerts — get notified the moment a chase card hits your target
  • 📊 Live prices from TCGPlayer & CardMarket as listings appear
  • 📈 Historical charts — track every card's price movement over time
  • 💰 Portfolio tracking — see what your collection is actually worth
  • 💬 Community discussion in our Discord

Get Started:


This article is for informational purposes only and is not financial advice. Details reflect information available at the time of writing; release dates, pricing and set contents are subject to change. Card prices are volatile — always verify current market pricing before buying or selling.