One Piece TCG — API Guide

also: Swagger Home

Getting started

A JSON API for One Piece TCG card data, plus a deck builder.

Base URL in production is https://tcg.harun.app; the examples below use http://127.0.0.1:8000 for local development — swap whichever applies. Every endpoint lives under /api/ and returns JSON.

This page is a hand-written companion to the auto-generated Swagger UI — that one is complete and always in sync with the code, but if you'd rather read prose than an OpenAPI schema tree, you're in the right place.

Authentication

Every request needs one of the following:

MethodHow
API key Authorization: Api-Key <your-key> header — what a client app sends.
Session login Log in at /api-auth/login/ or /admin/ in a browser — lets you browse /api/ directly with your existing admin account, no key needed.
curl -H "Authorization: Api-Key <your-key>" \
  http://127.0.0.1:8000/api/cards/
A logged-in session is also how the visual deck builder authenticates its own API calls from the browser — no key embedded in page JS.

Pagination

List endpoints return 25 results per page:

{
  "count": 2634,
  "next": "http://127.0.0.1:8000/api/cards/?page=2",
  "previous": null,
  "results": [ ... ]
}

Fetch a later page with ?page=2. Detail endpoints (/api/cards/OP01-001/-style ids) aren't paginated — they return a single object.

Filtering, search & ordering

Three separate query-param mechanisms, not all available on every endpoint:

MechanismParamAvailability
Field filters ?field=value Only on endpoints that declare them — see each section below. Unrecognized params are silently ignored.
Search ?search=term Only Cards supports this today (matches name, card number, effect text, distribution notes).
Ordering ?ordering=field (prefix - for descending) Available everywhere. Cards restricts it to card_number, name, cost, power; every other endpoint allows ordering by any of its own response fields.

Errors & rate limits

StatusMeaning
400Validation failed — body is a dict of field name → list of messages (or non_field_errors).
403Missing/invalid API key and no session login, or a CSRF failure on a session-authenticated write.
404Not found.
429Rate limit exceeded.
// 400 example — from POSTing an illegal deck body
{
  "cards": ["EB01-022 isn't in leader OP01-002's colors."]
}
120 requests/minute, tracked by IP. This applies to API-key traffic too — an API key doesn't authenticate a Django user, so it's throttled the same as any anonymous request. Only a real logged-in session escapes it.

Cards

A Card is the rules/stats definition — shared across every printing of it. Full CRUD exists (this is a plain viewset), but in practice you'll mostly read from it; writes are for admin/tooling use.

GET /api/cards/

Lean list serializer — enough to render a grid without pulling every print's full detail.

ParamMatches
categoryLEADER / CHARACTER / EVENT / STAGE
card_setSet code (e.g. OP01) — matches the card's home set, any print's set, or a set it's bundled into.
colorColor name, e.g. Red (case-insensitive exact match).
traitTrait name, e.g. Straw Hat Crew.
attributeAttribute name, e.g. Slash.
rarityMatches any of the card's prints, e.g. SEC.
art_styleMatches any of the card's prints, e.g. ALTERNATE_ART.
block_iconExact, e.g. 5.
has_erratatrue / false.
cost_min / cost_maxInclusive range on cost.
searchFree text over name, card number, effect text, distribution notes.
orderingcard_number, name, cost, power (prefix - for descending).
curl -H "Authorization: Api-Key <your-key>" \
  "http://127.0.0.1:8000/api/cards/?category=LEADER&color=Red&search=Luffy"

GET /api/cards/{card_number}/

Full detail — adds traits, effect_text, trigger_text, the complete errata history, and every prints entry (art variants, rarity, image) for that card.

Prints

A CardPrint is one specific printing of a card — original art, an alternate-art parallel, or a reprint added later by another product. Rarity and image live here, not on the card, because they can differ per printing.

GET /api/prints/

ParamMatches
rarityExact, e.g. SR.
card_setExact set id this print originally belongs to.
art_styleExact, e.g. MANGA.
confirmed_on_site / art_style_confirmedtrue / false.
orderingAny field on the print (e.g. variant, rarity).

Sets

GET /api/sets/ — every product (booster, starter deck, or promo), with code, name, set_type, release_date. No field filters; ordered by release date by default.

Colors / Attributes / Traits

Small reference lookups, mostly useful for populating filter dropdowns or a legend in a client app — no field filters on any of them:

EndpointFields
/api/colors/name, hex_code
/api/attributes/name, icon
/api/traits/name

Decks

A deck is one Leader plus a 50-card body. There are no real end-user accounts yet, so a deck saved via an API key stays anonymous (owner is null) — only a deck built through a real logged-in session gets an owner, and from then on only that owner can edit or delete it. Anonymous (unowned) decks stay editable by anyone holding the key.

GET /api/decks/

Filter with ?leader=OP01-002 or ?owner=<user id>. Ordered by most recently updated.

POST /api/decks/

Send the leader and the full card list together — card is a card_number, not a database id:

curl -X POST http://127.0.0.1:8000/api/decks/ \
  -H "Authorization: Api-Key <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Law Aggro",
    "leader": "OP01-002",
    "cards": [
      {"card": "OP01-003", "quantity": 4},
      {"card": "EB01-012", "quantity": 2}
    ]
  }'

Response:

{
  "id": 7,
  "name": "Law Aggro",
  "leader": "OP01-002",
  "cards": [
    {"card": "OP01-003", "quantity": 4, "print": 42, "print_detail": { ... }},
    {"card": "EB01-012", "quantity": 2, "print": 55, "print_detail": { ... }}
  ],
  "owner": null,
  "card_count": 6,
  "is_complete": false,
  "created_at": "2026-07-25T21:17:20Z",
  "updated_at": "2026-07-25T21:17:20Z"
}

is_complete only turns true at exactly 50 non-leader cards — a deck can be saved and resaved mid-build without being rejected. What does get rejected (400) regardless of progress:

Off-color cardEvery card's colors must be a subset of the leader's colors.
Copy limitMore copies of a card than its max_copies (4, unless the card overrides it).
Duplicate entryThe same card listed twice — combine into one entry with the total quantity.
Leader misuseA Leader card in the 50-card body, or a non-Leader card used as leader.
Mismatched printA print id that belongs to a different card than the entry claims.

Choosing artwork

Add "print": <id> to any entry to pick a specific art variant (id comes from that card's prints list on the card detail endpoint). Leave it out and the card's standard print is used automatically.

{"card": "OP01-033", "quantity": 2, "print": 186}

PUT /api/decks/{id}/

Same body shape as create. Always send leader and cards together — a partial update touching only one of them is rejected, since the two are validated as a pair.

DELETE /api/decks/{id}/

Only the deck's owner can do this once it has one; an unowned deck can be deleted by anyone holding the key.