SleevedSleeved Docs

API Overview

The power of Sleeved, at your fingertips.

Base URL

All API endpoints are prefixed with:

https://api.sleeved.gg/v1

Supported Games

The API currently supports four games:

GameSlug
Digimon Card Gamedigimon
Gundam Card Gamegundam
Grand Archive TCGgrand-archive
Chrono Core TCGchrono-core

Use these slugs in path parameters wherever a :slug is shown in an endpoint.

Authentication

Most endpoints require an API key passed in the X-API-Key request header. The Get Deck endpoint is the exception — it is unauthenticated so that Tabletop Simulator and similar tools can fetch deck data directly. See Authentication for details on key format and error handling.

Response Format

Success

All successful JSON responses wrap the payload in a data field:

{
  "data": { ... }
}

Errors

Error responses use an error field with a human-readable message:

{
  "error": "Invalid API key"
}

Common HTTP status codes:

StatusMeaning
200Request succeeded
400Bad request — invalid parameters
401Authentication failed
404Resource not found
429Rate limit exceeded

Text Exports

The Get Deck endpoint supports different export formats via the format query parameter:

  • format=json (default) — Structured output for all use-cases
  • format=text — For importing to platforms or simulators like Untap.in, Limitless TCG, DCGO, etc.
  • format=ga-tts — Grand Archive only; enriched card data for Tabletop Simulator

Content Negotiation Convention

Sleeved uses the ?format= query parameter to select representations on resources that can return non-JSON formats. The API does not honor the HTTP Accept header for content negotiation. This is a deliberate convention: query-parameter selection is curl-friendly, easy to share in bug reports and integration code, and unambiguous to debug from a browser address bar.

Two related rules to be aware of:

  • JSON responses always wrap the payload in { data: ... }. Non-JSON responses (e.g. text/plain from format=text) return raw content with no envelope — data: does not appear in the body.
  • Future endpoints with multi-format outputs will follow this same ?format= convention rather than adding Accept-header negotiation. If a new endpoint needs text, csv, or another format, it will expose them via ?format= for consistency.

Versioning

All routes are prefixed with /v1/. Breaking changes will be introduced under a new version prefix; the current version will continue to be served during any transition period. See API Contract Guarantees for the full versioning, deprecation, and breaking-change policy — including the 6-month deprecation window, RFC 8594 Deprecation/Sunset headers, and the per-endpoint /v2 coexistence model.

Browsing and Bulk-Fetching Public Decks

Two endpoints work together to let a partner enumerate and mirror a game's public decks without one request per deck:

  1. List public decks (GET /decks/public) returns paginated summaries — id, gameId, createdAt, updatedAt — for every public deck in a game. No deck name, owner identity, card contents, or engagement counters. Requires the decks:browse:public scope.
  2. Get deck details (POST /decks/details) takes up to 50 ids from step 1 and returns a minimal per-deck object — id, gameId, name, cards (each {cardId, quantity, zoneId}) — plus unresolved. Still no owner identity or timestamps. Requires the decks:read:public scope.
  3. For a single deck by id (outside a listing/bulk flow), use Get deck directly. This is the only one of the three that returns the full deck shape, including ownerDisplayName.

Match bulk-detail results by id, never by array position. POST /decks/details returns decks in unspecified order. Build a lookup keyed on each entry's id field before consuming the response.

name is untrusted, user-authored text — escape on render. Both POST /decks/details and GET /decks/:deckId return name (the listing endpoint, GET /decks/public, still does not). name — and, on GET /decks/:deckId, ownerDisplayName — comes straight from Firestore with no server-side escaping. HTML-escape (or otherwise sanitize) name, ownerDisplayName, and any other user-authored string field before rendering it in your own UI.

A missing id behaves differently on the two deck-fetch endpoints. GET /decks/:deckId (the single-deck endpoint) returns 404 for a deck that doesn't exist or isn't public. POST /decks/details never does this — every requested id that can't be resolved (malformed, private, or nonexistent, indistinguishably) is placed in the response's unresolved array instead, and the batch call still returns 200. If your integration expects a 404 per deck from the bulk endpoint, it will hang waiting for one that never comes — check unresolved instead.

updatedSince is for incremental sync, with caveats:

  • It only combines with the default sort=updatedAt — any other sort value with updatedSince set returns 400.
  • The updatedAt >= updatedSince boundary is inclusive. Store the updatedAt of the last deck you processed and pass it back as updatedSince on your next sync — expect to see that same deck again in the response and dedupe it client-side.
  • Timestamps are compared using Sleeved's server clock, not the requesting client's. Don't assume your local clock and Sleeved's are in lockstep to the second; the inclusive boundary is what makes this safe even with modest clock drift.
  • Delta syncing alone cannot detect deletions or unpublishing. A deck that stops being public no longer appears in any updatedSince window — it simply vanishes from future pages rather than showing up with a tombstone. Periodically run a full (non-incremental) enumeration of GET /decks/public and diff it against your local mirror to catch decks that were removed or made private since your last full pass.

Next Steps