SleevedSleeved Docs

API Contract Guarantees

Versioning, deprecation, breaking-change, and stability rules partners can rely on when integrating with the Sleeved API.

This page defines what partners can rely on when integrating with the Sleeved API: which changes Sleeved considers safe to ship without warning, which changes require advance notice, and how long that notice period is.

If you read nothing else: /v1 endpoints are stable, additive changes ship without notice, breaking changes ship through /v2 with at least 6 months of overlap and Deprecation + Sunset response headers on the /v1 endpoints.

Versioning

All public API routes are prefixed with a major version: /v1, /v2, etc. The current version is /v1.

  • The version prefix is part of the URL. There is no Accept header content negotiation; the version is determined by the URL path.
  • The OpenAPI spec's info.version field tracks the major + minor + patch shape (1.0.0 today). Patch and minor bumps are additive and do not change the URL prefix; major bumps move to a new URL prefix (/v2).
  • New games added to the public allowlist are additive. They do not trigger a new major version.

Additive vs Breaking Changes

The following changes are additive and may ship without notice:

  • New endpoints added under the current version prefix
  • New optional request fields, query parameters, or headers
  • New optional response fields
  • New enum values in response payloads (for fields whose enums are documented as open-ended — e.g. game slugs as new games are onboarded)
  • New error subcodes or finer-grained error messages, as long as the existing error string and status code are still produced for the same condition
  • New examples or documentation prose in the OpenAPI spec
  • Performance improvements that do not change response shape
  • Data-correctness fixes to previously incorrect or defaulted response field values, where the field's type and shape are unchanged — see Zone-Resolution Correctness Fix below for a concrete example

The following changes are breaking and require advance notice (see Deprecation Timeline):

  • Removing an endpoint, response field, or query parameter
  • Renaming a field or operationId
  • Changing a field's type, format, or null-tolerance
  • Narrowing the set of accepted values for a request field, query parameter, or path parameter
  • Adding a new required request field
  • Removing an enum value from a response or changing its meaning
  • Changing the URL of an existing endpoint (including version-prefix changes — see /v2 precedent below)
  • Changing the meaning of an existing status code on an existing endpoint

If you are unsure whether a planned change is additive or breaking from your integration's perspective, write engineering@endlessgalaxy.dev before deploying against it.

Deprecation Timeline

When a breaking change ships, the old behavior remains available for at least 6 months from the announcement date. During the deprecation window:

  • The deprecated endpoint or field continues to work and return correct responses
  • The deprecated endpoint emits an RFC 8594 Deprecation response header containing the date deprecation was announced
  • The deprecated endpoint emits a Sunset response header containing the date after which it will stop working
  • The replacement endpoint or field is documented with a migration link from the deprecated entry's documentation

Example response headers on a deprecated endpoint:

Deprecation: Sun, 01 Jan 2026 00:00:00 GMT
Sunset: Wed, 01 Jul 2026 00:00:00 GMT
Link: <https://docs.sleeved.gg/api/contract-guarantees>; rel="sunset"

Partners are notified by email at the announcement and again 30 days before sunset. The current public changelog is also updated.

/v2 Precedent

When /v2 ships, all /v1 endpoints continue to work for the full 6-month deprecation window — partners are not forced to migrate every endpoint at once.

Sleeved uses per-endpoint coexistence for the major-version transition: /v1/games/{slug}/cards and /v2/games/{slug}/cards may serve different shapes simultaneously during the transition window. This means partners can migrate one endpoint at a time rather than rewriting their entire integration in a single deploy.

The transition shape is:

  1. /v2 is announced. All /v1 endpoints remain unchanged for the full deprecation window.
  2. /v1 endpoints begin emitting Deprecation and Sunset headers pointing to their /v2 equivalents.
  3. After the sunset date, /v1 endpoints are removed.

Game additions and removals do not trigger /v2. The game allowlist is part of the /v1 contract; new games appear in /v1/games/{slug}/... paths additively.

Game Allowlist Policy

The set of games available via the public API is governed by PUBLIC_API_GAME_SLUGS (canonical source: packages/lib/src/games.ts). The current allowlist is published in the API overview.

  • Adding a game is additive. New games appear under existing endpoints (/v1/games/{newSlug}/cards, etc.) without a version bump.
  • Removing a game is breaking. A game removal goes through the deprecation timeline above: 6 months of Deprecation/Sunset headers on the affected paths, with the removal date announced in advance.
  • Internal/playtest games (e.g. chrono-core-playtest) are intentionally excluded from the allowlist and are not part of the contract. They may appear and disappear without notice.

Status Codes Contract

The following status codes are stable parts of the contract — partner SDKs may switch on them:

StatusMeaningStability
200Request succeededStable
400Bad request — invalid parameters, malformed cursor, unsupported formatStable
401Authentication failed (missing or invalid API key)Stable
404Resource not found, or share token failed (token does not exist, expired, or for a different deck)Stable
429Rate limit exceededStable
500Internal server error — retry with exponential backoffStable category; specific upstream errors may bubble through

Within the stable categories, the body error string is also stable for the documented cases. See Authentication for the three authentication error shapes and Rate Limiting for the 429 retry contract.

We may add new 4xx subcodes (e.g., a more specific 422-style validation error) as additive changes — the existing 400 path continues to be produced for the same conditions.

Pagination Contract

Cursor pagination on browseCards follows these rules:

  • Cursors are opaque. Do not parse them, base64-decode them, or assume any internal structure. The cursor format may change between versions or even within a single version (cursors are server-generated and partner-consumed only).
  • Cursors expire. A cursor is valid for the same parameters that produced it (game slug, hitsPerPage). Restarting pagination with a different hitsPerPage invalidates any stored cursor — request a fresh page-1 instead.
  • Cursors are not stable across resyncs. A cursor returned today may not be valid tomorrow if the underlying index has been rebuilt. If you receive a 400 with a cursor-related error, restart pagination from page 1.
  • Ordering is stable within a single pagination walk (start at page 1, follow cursors to the end). The order is not guaranteed to be stable across separate pagination walks — the underlying Algolia index may reorder records between walks. If your integration depends on a specific order across runs, sort the results client-side after collection.
  • Adding fields to hits is additive. New fields may appear in hits[i] records without notice; existing fields keep their shape and meaning per the breaking-change rules above.

ChronoCore Public-Decks Crawl Profile (Advisory)

This section describes typical request volume for the List public decks + Get deck details pair when used by ChronoCore's public-corpus crawl. It is advisory, not a version-pinned commitment — the numbers describe a supported usage pattern under the existing generic rate-limit contract (see Rate Limiting), not a separately guaranteed quota.

Expected steady-state profile, using updatedSince and bulk-detail:

  • Full corpus crawl — a forward-looking headroom estimate: ≈20 listing pages (at limit=50) + ≈10 bulk-detail calls (50 ids each), sized for a ~1,000-deck corpus. The real chrono-core public corpus is ~65 decks today; the estimate deliberately sizes for future growth, not current load.
  • Incremental refresh (steady state, quiet corpus) — ≈1 listing GET with updatedSince set to the last-observed max updatedAt, plus 0–1 bulk-detail calls (only fired when the delta listing returns any ids).

Legacy ceiling (tolerated, not sanctioned). Before updatedSince and bulk-detail existed, a full refresh meant one GET /decks/:deckId call per deck. That pattern — up to ~20 listing pages plus up to ~1,000 individual detail GETs per 30-minute refresh — still works and is not being deprecated, but it is a tolerated upper bound, not the recommended pattern going forward. Both the legacy profile and the new profile comfortably fit inside a single per-key rate-limit window (typically 3,000 requests / 15 minutes); the new profile just leaves a much larger operating margin. New integrations should use the listing + bulk-detail pair described above rather than the legacy per-deck GET loop.

Zone-Resolution Correctness Fix (Chrono Core)

Chrono Core decks returned by Get deck and Get deck details now resolve each card's zoneId against the Chrono Core card catalog instead of trusting only the raw stored value. This is a data-correctness fix, not a breaking change:

  • zoneId is an open string field (not an enum) on both endpoints — the breaking-change clause covering "removing an enum value or changing its meaning" does not apply here, because there is no enum to change the meaning of.
  • Only cards with an absent or unrecognized stored zone are affected. A card whose stored zone was already a recognized (even if factually wrong) value keeps that stored value — this is a deliberate "savedZone wins" limitation, not an oversight. The fix closes the gap for missing/garbage zone data; it does not attempt to correct every possible misclassification.
  • The field's type and shape are unchanged (zoneId: string, same position in the same cards[] array entry) — only the value for a subset of previously-defaulted entries can change.

Transient-consistency note for delta-crawl consumers. If your integration dedupes incremental List public decks results by (id, updatedAt) (as recommended in the API Overview), a deck whose zone was affected by this fix keeps serving its old, pre-fix defaulted zone in your local cache until that deck's updatedAt changes again and you re-fetch it via Get deck details. This is expected — the corrected value becomes visible the next time you observe that deck through the normal update-driven refresh cycle, the same as any other field correction would.

What Is NOT Part of the Contract

The following are not stable and may change without notice:

  • The internal storage path scheme for card images (the cardImageUrl field is stable; the URL it contains may change CDN, bucket, or path layout)
  • Internal log format, including the reason=missing|not_found|revoked operator-side log distinctions
  • The exact wording of human-readable error messages outside the documented set in Authentication and Rate Limiting — partners should switch on status code + the documented error strings, not on free-text error prose
  • Performance characteristics (latency, throughput, exact cache TTLs), as long as documented rate-limit quotas are honored
  • Internal Sleeved-specific endpoints under paths other than /v1 (or future /v2) — those are not part of the partner API
  • The fumadocs-openapi rendering of these docs (UI may change without contract impact)

Reporting Contract Violations

If you observe behaviour that contradicts a guarantee on this page — a breaking change shipped without notice, a Deprecation header missing on a now-deprecated endpoint, a status code changing meaning — please write engineering@endlessgalaxy.dev with the request you sent, the response you received, and the contract clause you believe was violated. Sleeved treats contract regressions as bugs, not negotiable trade-offs.