Card catalogs and translations, plus opt-in account sync for external tools, simulators and community projects.

API key required
These docs are public, but every /v1/* endpoint requires a key issued personally by the ExBurst creator. Want one? Reach out via exburst.dev (Discord link in the footer there).

Authentication

Send your key on every request, either way works:

curl -H "X-Api-Key: exb_live_..." https://partner-api.exburst.dev/v1/games
curl -H "Authorization: Bearer exb_live_..." https://partner-api.exburst.dev/v1/games

Keep the key server-side. Do not ship it inside a public web page or mobile app binary. If a key leaks, tell me and I rotate it.

Rate limits

Budgets are counted in units, not raw requests. Cheap endpoints cost 1 unit; bulk endpoints cost more. Default budget per key: 60 units/minute and 5000 units/day (custom budgets are possible per key).

Every response carries X-RateLimit-Limit-Minute, X-RateLimit-Remaining-Minute, X-RateLimit-Limit-Day, X-RateLimit-Remaining-Day. Over budget you get 429 with a Retry-After header.

Be a good citizen
Responses are cacheable and most data changes at most daily. Sync once per day with the bulk endpoints, cache locally, and serve your own users from your own infrastructure. Do not proxy per-user traffic straight to this API.

Endpoints

EndpointCostDescription
GET/v1/games1Supported games, their scope model, copy caps.
GET/v1/{game}/cards1Cards, cursor paginated.
GET/v1/{game}/cards/all20The whole catalog in one response. For daily sync jobs.
GET/v1/{game}/cards/{cardno}1One card by card number.
GET/v1/{game}/translations2Translations for up to 120 specific cards.
GET/v1/{game}/translations/languages3Which translation languages exist for a game, with counts.
GET/v1/{game}/translations/manifest10Every approved translation for one game + language, with updatedAt stamps for delta sync.
GET/v1/{game}/archetypes3The archetype catalog: every valid opponent key for result reports, with name, icon and color.
GET/v1/{game}/sync2A user's profile + decklists via their personal sync token. One call per user per 30 s.
POST/v1/{game}/sync/result5Report one game (win/loss/draw, opponent archetype) for a synced deck; it becomes a W/L row on that deck and in the player's Match Log. One report per user per 30 s.
PUT/v1/{game}/sync/decks/{deckId}5Update a user's deck (name, cards, sideboard) from your app, validated exactly like a site save. One update per user per 30 s.

{game} is one of: ua uaen opcg gundam naruto nrtb riftbound cyberpunk

Machine-readable spec: /openapi.json. Public, no key: /stats (also /stats.json) and /health.

Full parameter reference

GET /v1/games

No parameters. Each entry: id (the {game} path value), scopeLabel ("Series", "Set" or "Regulation"), scopes (the fixed scope list for regulation-scoped games, null for series games), maxCopies (deck copy cap), leaderType (the card type value marking leaders, or null).

GET /v1/{game}/cards

ParamInTypeRequiredDefaultDetails
gamepathenumyesSee the games list above.
limitqueryintegerno1001 to 200 cards per page.
cursorquerystringnoOpaque cursor, copy it verbatim from the previous page's meta.nextCursor. Omit for the first page. Do not build cursors yourself.
seriesquerystringnoScope filter. For series/set games (ua, uaen, gundam, naruto, nrtb, riftbound, cyberpunk): a series code exactly as returned in each card's series.code (e.g. eb01). For opcg: a regulation code from /v1/games scopes (e.g. 226 for Standard).
searchquerystringnoCase-insensitive substring match on the card name. Max 80 chars; the characters , ( ) * % _ \ are treated as spaces.

meta: nextCursor (string, null on the last page), total (estimated total for the filter, first page only, null on cursor pages), limit (echo). A page can contain FEWER than limit cards (rows without a usable image are skipped); keep paging until nextCursor is null, never until an empty page.

GET /v1/{game}/cards/all

No query parameters. Returns the entire published catalog, ordered by card number. meta: count, generatedAt. Response is served from a 1 hour edge cache; intended for daily sync jobs, never per-user calls.

GET /v1/{game}/cards/{cardno}

cardno is the BARE card number as printed and as returned in cardNo (e.g. OP01-001), never prefixed with the game id. Alternate prints have their own suffixed numbers (e.g. OP01-001-ALT2) and are linked to their base print via sharedKey / variantOf. Unknown numbers return the 404 envelope.

GET /v1/{game}/translations

ParamInTypeRequiredDetails
gamepathenumyes
langquerystringyesA language code that exists for this game; discover them via /v1/{game}/translations/languages.
idsquerystringyesComma-separated bare card numbers, max 120 (duplicates removed). Need more than 120? Use the manifest and filter locally.

meta: requested (ids after dedup), found (translations that exist). Cards with no translation in that language are simply absent from data: an empty result is normal, not an error.

Two sources, one contract. Results merge ExBurst's OFFICIAL translations with COMMUNITY translations (player-submitted, ranked by votes; the top-voted proposal per card wins). Official always takes precedence per card; community fills the gaps. Each item carries source: "official" | "community" and, for community items, communityScore (net votes) so you can apply your own trust threshold. Community content evolves with votes: re-sync daily and the current best proposal follows automatically.

GET /v1/{game}/translations/languages

No query parameters. Each entry: language (the value to pass as lang), count (distinct cards covered by official OR community), officialCount, communityCount, latestUpdatedAt (most recent change across both sources, useful to skip a sync when nothing moved).

GET /v1/{game}/translations/manifest

ParamInTypeRequiredDetails
gamepathenumyes
langquerystringyesSame codes as above.

The FULL dump for one game + language, ordered by card number, official + community merged (see the batch endpoint for the merge rule). meta: count, official, community, generatedAt. This is the endpoint your daily sync should use.

GET /v1/{game}/archetypes

No query parameters. The per-game archetype catalog the site's own pickers use: each entry is key (e.g. db_OP01-060, the value to send as an opponent archetype), name, icon (card image URL or null), color, block, series. meta: count, maxOpponentArchetypes (how many keys one result may name: 1 for single-leader games such as opcg and riftbound, 3 for cyberpunk Legends, up to 16 for tag-archetype games), generatedAt. 1 hour edge cache: fetch it once per session, not per game.

curl -H "X-Api-Key: $KEY" "https://partner-api.exburst.dev/v1/opcg/archetypes"

GET /v1/{game}/sync

ParamInTypeRequiredDetails
gamepathenumyes
tokenquerystringyesThe user's personal sync token, generated on their ExBurst profile (Integrations). Treat it like a password: it grants read access to that user's decklists.

Returns data.user (id, username, avatar, selectedBadge, selectedAvatarBackground, level) and data.decklists (up to 50, newest first): id, name, content (parsed decklist JSON, ready to consume), archetypes (the deck's own archetype keys, same catalog as /v1/{game}/archetypes), coverCard, modifiedAt. meta: count, cooldownSeconds, nextAllowedAt.

Cooldown: 1 call per user per 30 s (per API key + sync token pair), on top of your unit budget. A second call inside the window gets 429 with Retry-After; a wrong token gets 404 and does NOT arm the cooldown. Responses are private, no-store: never cache them shared-side, never log the token.

curl -H "X-Api-Key: $KEY" "https://partner-api.exburst.dev/v1/opcg/sync?token=USER_SYNC_TOKEN"

POST /v1/{game}/sync/result

For simulators and trackers: report each game played with a synced deck. It becomes a win/loss/draw row on that deck, the same rows the player sees in their ExBurst Match Log, in the deck's W/L tab and as the record on My decks. Entirely opt-in for your app; one of two tightly scoped sync writes.

Typical flow: the player pastes their sync token, you call /v1/{game}/sync to list their decks, /v1/{game}/archetypes once to get the opponent keys, then POST one result per finished game with the deckId they played.

JSON body:

FieldTypeRequiredDetails
tokenstringyesThe user's sync token (same as the sync endpoint).
deckIdnumberyesA deck id from /v1/{game}/sync. Must belong to the token's user (404 otherwise).
resultstringyeswin / loss / draw (aliases accepted: W, L, T, tie).
matchIdstringrecommendedYour own unique id for the game (1 to 64 chars of A-Z a-z 0-9 _ . : -). Retries with the same matchId are acknowledged (data.duplicate: true) and never double-counted.
opponentArchetypesstring[]noOpponent archetype keys from /v1/{game}/archetypes, at most maxOpponentArchetypes for the game. An unknown key is a 400 naming it. Omit for an unknown opponent.
opponentArchetypestringnoShorthand for a single key.
opponentCardnostringnoAlternative to a key: the bare card number of the opponent's leader or legend (alternate prints resolve to their base print). A card that is not an archetype card is a 400.
wonDicebooleannoWon the dice roll.
notestringnoFree note (max 280).
playedAtISO datetimenoWhen that game finished. Defaults to now.

Unknown fields are rejected with a 400 (the 2026-09 session fields opponent, onPlay, eventName, eventType, eventDate, formatLabel were retired). Response data: duplicate, deckId, matchId, record (id, result, opponentArchetypes, wonDice, note, playedAt) and totals (wins, losses, draws across every row on that deck). A deck keeps its 200 most recent rows. Locked decks and official starter lists are refused (403).

Cooldown: 1 report per user per 30 s (per API key + token), armed on success. The matchId duplicate check runs BEFORE the cooldown, so retrying a timed-out request is always safe and never reads as a lockout. Reporting a whole best-of-3 at once? Space the calls by 30 s, or report each game as it ends.

curl -X POST -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"token":"USER_SYNC_TOKEN","deckId":7,"result":"win","opponentArchetypes":["db_OP01-060"],"wonDice":true,"playedAt":"2026-09-24T18:30:00Z","matchId":"game-8842"}' \
  "https://partner-api.exburst.dev/v1/opcg/sync/result"

PUT /v1/{game}/sync/decks/{deckId}

Push a deck edit made in your app back onto the user's ExBurst account. The update runs through the SAME pipeline as a save on the site: full validation of the deck content, automatic archetype detection, derived colors. If validation fails you get a 400 with the exact reason; locked decks and official starter lists are refused (403); a deck that is not the token user's own is a 404.

JSON body (send only what changed, at least one field):

FieldTypeDetails
tokenstringRequired. The user's sync token.
namestringDeck name (1 to 200 chars).
slotsarrayThe main deck, in the exact shape you READ from /v1/{game}/sync's content.slots: [[cardId, { count, card }], …]. Round-trip what you read, change the counts/entries, send it back.
sideboardSlotsarraySame shape, for the sideboard.
descriptionstringDeck notes.
selectedItemsstring?Hidden-slot card id (e.g. the Riftbound Champion). null clears it.

Not writable from a partner app, ever: visibility (public/private), locks, folders, lineage. Response: the updated deck in the site's own Deck shape (with the freshly recomputed archetypes and colors). Cooldown: 1 update per user per 30 s (per API key + token). Responses are private, no-store.

curl -X PUT -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"token":"USER_SYNC_TOKEN","name":"Aggro v2","slots":[["OP01-001",{"count":4,"card":{...}}]]}' \
  "https://partner-api.exburst.dev/v1/opcg/sync/decks/7"

Available languages

Live truth is always /v1/{game}/translations/languages. Snapshot on 2026-08-07:

GameLanguagesNotes
opcgfr (1321), ja (2659)French card texts + pre-rendered French card images; Japanese scans/texts.
uaja (10713)Japanese source scans/texts backing ExBurst's EN-on-JA overlay.
gundamja (1816)Japanese source data.
narutofr (455)French card texts.
uaen, nrtb, riftbound, cyberpunknone yetCoverage grows over time; poll the languages endpoint.

A translation row means "this card's content in that language". Coverage is per card: a language can cover only part of a catalog.

Response shape

Every success is { "data": ..., "meta": { ... } }. Cached responses carry X-Cache: HIT|MISS; the request id rides the X-Request-Id header. Every error is:

{ "error": { "code": "rate_limited", "message": "...", "status": 429, "requestId": "..." } }

Stable code values: unauthorized (401), forbidden (403, revoked key), not_found (404), validation_failed (400), rate_limited (429), service_unavailable (503, includes the kill-switch), internal (500). Include the requestId when reporting a problem.

Card object, full field reference

One unified shape across all games; fields a game does not have are null.

FieldTypeMeaning
gamestringGame id.
cardNostringBare printed card number. Primary key within a game.
namestring?Card name.
image / imageFallbackurl / url?Card image (CDN) and optional legacy fallback.
seriesobject{ code, name } of the set/series. code is the value for the series filter.
color, rarity, type, traitstring?Core taxonomy. Multi-values are the printed string (split on / if you need tokens).
costint?Play cost (Energy / Cost / Chakra depending on the game).
powerint?Primary combat stat (Power / BP / HP / Life depending on the game).
ap, generatedEnergy, level, attributeint?/int?/int?/string?UA AP cost, UA energy generation, level, attribute/domain/faction.
powerCost, dmg, hp, lifeint?Riftbound power pips; NARUTO CARD GAME stat trio.
maxCopiesint?Per-card printed copy-limit override (game default lives in /v1/games).
effect, effectJastring?Effect text (and UA's Japanese original).
triggerobject{ kind, text }, both nullable.
publishedboolAlways true on this API (unpublished rows are filtered out).
marketPricenumber?Latest USD market price when tracked.
source, counter, block, link, rarityType, sourceTitlevariesPer-game extras: acquisition source, OPCG counter, block icon, Gundam link, UA rarity class, Gundam source title.
generateResources, isEddiablestring?Cyberpunk flags, "Yes" or null.
rulingsarray?Official Q&A entries { number, date, question, answer } (ua, uaen, gundam).
notes, notesUrlstring?OPCG errata/ban notes + link.
sharedKey, isPrimary, variantOf, variantLabelvariesAlternate-print linkage: all prints of one card share sharedKey; the primary print has isPrimary: true; variants point at it via variantOf and may carry a label ("Alt Rare", "Overnumbered").
updatedAtstring?Row last-modified stamp, usable for catalog delta sync.

Translation items: cardNo, language, fields (name, effect, trait, link, attribute, attributedata, triggerText, each nullable), imageUrl (pre-rendered translated card image when one exists, else null), updatedAt, source ("official" or "community"), communityScore (net votes of the winning community proposal, null for official rows).

Add the translations to your site, step by step

The recommended integration is a daily mirror: your server syncs once a day, your users are served from YOUR infrastructure.

  1. Discover languages once: GET /v1/opcg/translations/languages → pick the codes you want.
  2. Daily cron: pull the manifest per language and the catalog: /v1/opcg/translations/manifest?lang=fr + /v1/opcg/cards/all.
  3. Diff: compare each item's updatedAt (or imageUrl, the URLs are versioned) with your previous pull; download only changed images and host them yourself.
  4. Render: for a card in language X, if the translation has an imageUrl, show that image instead of the base card image. If it only has fields, keep the base image and show the translated texts next to it (name, effect, trait).
  5. Join by cardNo: the manifest and the catalog share the same bare card numbers.
// Node example: build a cardNo → translation map, mirror changed images
const H = { 'X-Api-Key': process.env.EXBURST_KEY };
const base = 'https://partner-api.exburst.dev/v1/opcg';

const manifest = await (await fetch(base + '/translations/manifest?lang=fr', { headers: H })).json();
const byCardNo = new Map(manifest.data.map(t => [t.cardNo, t]));

for (const t of manifest.data) {
  if (!t.imageUrl) continue;                      // text-only translation
  if (previouslySynced.get(t.cardNo) === t.updatedAt) continue; // unchanged
  await mirrorToMyCdn(t.imageUrl, 'cards/fr/' + t.cardNo + '.webp');
}
// In your app: card image = myCdn('cards/fr/' + cardNo) when byCardNo.has(cardNo)

Attribution is required in your UI: "Card data and translations by ExBurst" linking to exburst.dev.

More examples

Page through One Piece cards

curl -H "X-Api-Key: $KEY" "https://partner-api.exburst.dev/v1/opcg/cards?limit=100"
# → { "data": [ ...cards ], "meta": { "nextCursor": "T1AwMS0wOTk=", "total": 6697, "limit": 100 } }
curl -H "X-Api-Key: $KEY" "https://partner-api.exburst.dev/v1/opcg/cards?limit=100&cursor=T1AwMS0wOTk="

Filter one set, search a name

curl -H "X-Api-Key: $KEY" "https://partner-api.exburst.dev/v1/opcg/cards?series=eb01&limit=50"
curl -H "X-Api-Key: $KEY" "https://partner-api.exburst.dev/v1/opcg/cards?search=luffy&limit=20"

Translations for specific cards

curl -H "X-Api-Key: $KEY" \
  "https://partner-api.exburst.dev/v1/opcg/translations?lang=fr&ids=EB02-001,EB02-002,OP01-001"

Terms of use

Card names, texts and images belong to their respective publishers. This API gives you the data ExBurst has aggregated for interoperability; you are responsible for your own use of it. Required: visible attribution ("Card data and translations by ExBurst") with a link to exburst.dev in any product using this API. Keys are personal, non-transferable, and can be revoked at any time.

Changelog

DateChange
2026-09-24Results now land on the deck itself: POST /v1/{game}/sync/result adds a W/L row to the synced deck (the player's Match Log, deck W/L tab and My decks record), with opponent archetypes validated against the new GET /v1/{game}/archetypes catalog. Session fields retired; synced decklists now carry archetypes.
2026-08-07Deck sync-back: PUT /v1/{game}/sync/decks/{deckId} pushes deck edits from your app onto ExBurst, validated like a site save.
2026-09-04Detailed result sync: draws, dice and turn context, exact game times, and named event sessions are supported by POST /v1/{game}/sync/result.
2026-08-07Win/lose sync: POST /v1/{game}/sync/result reports game results into the player's ExBurst Match Log (opt-in for your app, idempotent via matchId).
2026-08-07New /v1/{game}/sync endpoint: fetch a user's profile + decklists with their personal sync token (1 call per user per 30 s).
2026-08-07New public Stats tab: per-game catalog sizes and per-language translation coverage, refreshed hourly (also at /stats.json).
2026-08-07Community translations integrated: translation endpoints now merge official + top-voted community proposals; items carry source and communityScore.
2026-08-07New /v1/{game}/translations/languages endpoint; full parameter reference, language table, card field reference and integration guide added to these docs.
2026-08-07Permanent base URL: partner-api.exburst.dev (single environment); docs restyled to the ExBurst V4 design.
2026-08-07Initial release: games, cards (list / bulk / detail), translations (batch / manifest).