Skip to content

API guide

Pierre edited this page Oct 29, 2026 · 1 revision

Discovery

URL Purpose
/api/docs Interactive FastAPI documentation
/api/openapi.json OpenAPI schema
/api/health API version and maintenance state

Use the schema from your running version for exact wire contracts. API endpoints and API models provide a source-derived reference for this documented revision.

Most routes require an authenticated user. /api/users and /api/admin/* require an administrator. Catalog IDs and printing IDs are local database identifiers; obtain them from the catalog APIs before changing stock.

Local login: token mode

POST JSON to /api/auth/login with username, password, and mode: "token". The response contains access_token, refresh_token, token_type: "bearer" and expires_in. Use Authorization: Bearer <access token> on subsequent requests.

For local scripts, this standard-library Python example prompts for credentials and avoids embedding them in source:

import getpass
import json
import urllib.request

base = input("Server URL: ").rstrip("/")
username = input("Username: ")
password = getpass.getpass("Password: ")
body = json.dumps({"username": username, "password": password, "mode": "token"}).encode()
request = urllib.request.Request(
    base + "/api/auth/login", data=body,
    headers={"Content-Type": "application/json"}, method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
    session = json.load(response)
request = urllib.request.Request(
    base + "/api/collection",
    headers={"Authorization": "Bearer " + session["access_token"]},
)
with urllib.request.urlopen(request, timeout=30) as response:
    collection = json.load(response)
print("Collection rows:", collection["total"])

Use HTTPS for credentials outside a trusted local setup. This example is for local accounts; it is not a substitute for the OIDC mobile/browser flow.

Refresh and logout

POST /api/auth/refresh with a refresh token in token mode. Rotation returns a replacement pair; retain the new refresh token. Reusing an older rotated token outside the short concurrency grace period revokes the token family. POST /api/auth/logout with the refresh token to end the session.

The default access lifetime is one hour and refresh lifetime is 90 days, sliding on rotation. Password changes, account disabling and server signing-key changes can end sessions earlier.

Browser cookie mode

Login with mode: "cookie" sets HttpOnly session cookies and returns the user. Unsafe cookie-authenticated requests must include X-TCG-CSRF: 1. Token-mode bearer requests do not need that CSRF header. Browser refresh/logout can use their refresh cookie; see the schema for optional body fields.

The refresh cookie is scoped under /api/auth; preserve the actual cookie attributes rather than treating it as a general JavaScript-readable token.

Catalog workflow

  1. GET /api/games for game IDs, available variants, filters and source capabilities.
  2. GET /api/games/{game_id}/sets for local sets.
  3. GET /api/cards?game=mtg&q=... for logical cards.
  4. GET /api/cards/{card_id} or its /printings route for exact printing IDs.
  5. POST /api/collection/adjust to change a confirmed variant.

Do not invent a printing ID from its collector number.

Stock example

After resolving a real printing ID, the adjustment body is:

{
  "printing_id": 123,
  "finish": "nonfoil",
  "condition": "NM",
  "language": "en",
  "delta": 1,
  "source": "web"
}

123 is a placeholder and must be replaced. finish must be valid for that printing/game. The response includes the resulting item and a transaction ID. A negative delta removes copies. An absolute PATCH can set quantity, notes or manual price; manual_price: null restores automatic prices and notes: null clears notes.

CSV and scanning

CSV import uses multipart field file, query mode=add|set, and optional dry_run=true. Even an HTTP 200 response can contain row errors; inspect errors and imported. Do not set a JSON Content-Type for multipart data.

The scan endpoint accepts JSON OCR lines, not images. Each line can include text, left, top, width and height, with coordinates normalized to 0–1. Maximum input: 200 lines, 300 characters per text. Its status is found, not_found, other_game or not_a_card. Candidate printings do not themselves change stock.

Pagination and repeated filters

Collection pages use items, total, page and page_size; defaults are page 1 and size 50, up to 500 collection rows. Catalog pages also use items with the same pagination metadata, up to 200 cards. Some other list routes have their own shape. Repeat query keys for lists, for example color=W&color=G or attr=types:Creature&attr=rarity:rare with a selected game. Encode spaces and non-ASCII query values normally.

Errors

Status Meaning in common paths
400 Invalid operation or malformed input handled by the route
401 Missing, expired or ended session
403 Missing role, disabled local login, or missing CSRF header
404 Missing user/card/printing/item/game
409 Stock conflict, duplicate account/job or protected SSO edit
413 Body or CSV too large
422 Request schema validation
429 Authentication rate limit; honor Retry-After
500 Unexpected backend error; inspect server logs
503 Load-triggered maintenance for ordinary users

Standard route errors normally use detail; FastAPI validation errors can carry a structured list. Avoid retrying non-idempotent stock additions automatically after an ambiguous network failure: first inspect the resulting collection/history to prevent duplicates. There is no documented general idempotency-key mechanism.

Implementation references

Clone this wiki locally