Skip to main content

API Overview

The Riftseer API is a read-mostly REST API that exposes Riftbound TCG card data. It powers the Riftseer frontend, Discord bot, and Reddit bot. It can also be used directly by third-party tools.

  • Base URL: https://api.riftseer.com
  • All versioned routes: /api/v1/...

Authentication​

Most endpoints are publicly accessible and require no authentication. The /api/v1/auth endpoints manage user sessions; authenticated requests should include Authorization: Bearer <access_token> where required. Admin mutations also require the token's user UUID to be listed in ADMIN_USER_IDS.


Request format​

Requests with JSON bodies use Content-Type: application/json. Admin image uploads use multipart/form-data. Protected routes additionally require Authorization: Bearer <access_token>.


Response format​

Successful resource responses return JSON. Collection endpoints wrap their results with count or pagination metadata, for example:

{
"count": 3,
"cards": [ ... ]
}

Single-resource responses return the object directly:

{
"object": "oracle",
"id": "...",
"name": "Sun Disc",
...
}

Rules objects carry "object": "oracle", physical editions carry "object": "printing", and relationship references carry "object": "oracle_ref".


Errors​

Errors return a JSON body with error (human-readable message) and code (machine-readable string):

{
"error": "Query parameter `name` is required",
"code": "MISSING_PARAM"
}
StatusMeaning
400Bad request — missing or invalid parameter
401Unauthenticated — missing or invalid token (auth routes only)
404Resource not found
500Internal server error

Versioning​

All routes are versioned under a path prefix (/api/v1/...). The version is part of the URL, not a header. If a breaking change is ever needed, a new /api/v2/... prefix will be introduced alongside v1 — old versions are not removed.

The interactive reference at /docs documents all active versions.


Framework​

The API is built with ElysiaJS deployed as a Cloudflare Worker. Elysia uses the CloudflareAdapter and a versioned sub-app pattern rather than .group():

// Each version is a standalone Elysia sub-app
const v1 = new Elysia({ prefix: "/api/v1" })
.use(metaRoutes(...))
.use(cardsRoutes(...))
.use(setsRoutes(...))
.use(decksRoutes(...))

// Mounted on the root app with CloudflareAdapter
export const app = new Elysia({ adapter: CloudflareAdapter })
.use(cors(...))
.use(v1)
.compile()

export default app

Route modules live in apps/api/src/routes/:

ModuleRoutes
meta.ts/health, /meta
cards.ts/cards, /cards/random, /cards/detail, /cards/:id, /cards/:id/text, /cards/by-slug/*, /cards/resolve, /printings/:id
sets.ts/sets
formats.ts/formats
decks.ts/decks, /decks/:id, /decks/:id/cards, /decks/:id/revisions, /decks/:id/invite, /decks/:id/collaborators, /decks/join/:code, /decks/import, /decks/:id/export
auth.ts/auth/register, /auth/login, /auth/refresh, /auth/logout, /auth/forgot-password, /auth/me, /auth/reset-password
admin.ts/admin/oracles/*, /admin/printings/*, /admin/sets/*, /admin/formats/*, /admin/rulings/*, /admin/reconciliation/*

Provider pattern​

Public card reads go through the CardDataProvider interface from @riftseer/core. The active implementation is SupabaseCardProvider, selected through the CARD_PROVIDER binding. Admin mutations use a service-role-backed repository so each RPC can update the live row, lock the changed fields, and append an audit event atomically.

This means the API has no opinion on where data comes from — swapping the provider requires no changes to route code.


Adding an endpoint​

  1. Add the handler to the relevant route module in src/routes/
  2. Annotate it with Elysia schema types (.query(), .body(), .response()) and a detail block (used for Eden Treaty types and static spec generation)
  3. Write a test in src/__tests__/routes/
  4. Update the relevant doc page in apps/api/docs/
  5. If the endpoint stores or exposes new personal data, review apps/web/src/views/privacy-view.tsx

Endpoints​

MethodPathDoc
GET/api/v1/healthMeta
GET/api/v1/metaMeta
GET/api/v1/cardsSearch
GET/api/v1/cards/randomCards
GET/api/v1/cards/detailCards
GET/api/v1/cards/:idCards
GET/api/v1/cards/:id/textCards
GET/api/v1/cards/by-slug/*Cards
POST/api/v1/cards/resolveCards
GET/api/v1/printings/:idCards
GET/api/v1/setsSets
GET/api/v1/formatsFormats
GET/api/v1/decksDecks
POST/api/v1/decksDecks
GET/api/v1/decks/:idDecks
PATCH/api/v1/decks/:idDecks
DELETE/api/v1/decks/:idDecks
PUT/api/v1/decks/:id/cardsDecks
GET/api/v1/decks/:id/revisionsDecks
POST/api/v1/decks/:id/inviteDecks
DELETE/api/v1/decks/:id/inviteDecks
POST/api/v1/decks/join/:codeDecks
POST/api/v1/decks/:id/collaboratorsDecks
DELETE/api/v1/decks/:id/collaboratorsDecks
POST/api/v1/decks/importDecks
GET/api/v1/decks/:id/exportDecks
POST/api/v1/auth/registerAuth
POST/api/v1/auth/loginAuth
POST/api/v1/auth/refreshAuth
POST/api/v1/auth/logoutAuth
POST/api/v1/auth/forgot-passwordAuth
GET/api/v1/auth/meAuth
POST/api/v1/auth/reset-passwordAuth
GET/api/v1/admin/audit-logAdmin
GET/api/v1/admin/reconciliationAdmin
POST/api/v1/admin/reconciliation/:id/confirmAdmin
POST/api/v1/admin/reconciliation/:id/dismissAdmin
POST/api/v1/admin/oraclesAdmin
PATCH/api/v1/admin/oracles/:idAdmin
DELETE/api/v1/admin/oracles/:idAdmin
POST/api/v1/admin/oracles/:id/restoreAdmin
GET/api/v1/admin/oracles/:id/relationshipsAdmin
PUT/api/v1/admin/oracles/:id/relationshipsAdmin
POST/api/v1/admin/printingsAdmin
PATCH/api/v1/admin/printings/:idAdmin
DELETE/api/v1/admin/printings/:idAdmin
POST/api/v1/admin/printings/:id/restoreAdmin
POST/api/v1/admin/printings/:id/regenerate-slugAdmin
GET/api/v1/admin/printings/:id/deltasAdmin
PUT/api/v1/admin/printings/:id/deltasAdmin
POST/api/v1/admin/printings/:id/imageAdmin
GET/api/v1/admin/printings/:id/legalitiesAdmin
PUT/api/v1/admin/printings/:id/legalitiesAdmin
GET/api/v1/admin/printings/:id/rulingsAdmin
POST/api/v1/admin/setsAdmin
PATCH/api/v1/admin/sets/:setCodeAdmin
DELETE/api/v1/admin/sets/:setCodeAdmin
GET/api/v1/admin/formatsAdmin
POST/api/v1/admin/formatsAdmin
PUT/api/v1/admin/formats/orderAdmin
PATCH/api/v1/admin/formats/:codeAdmin
DELETE/api/v1/admin/formats/:codeAdmin
PUT/api/v1/admin/formats/:code/zone-rules/:zoneAdmin
DELETE/api/v1/admin/formats/:code/zone-rules/:zoneAdmin
PUT/api/v1/admin/formats/:code/severities/:legality_statusAdmin
GET/api/v1/admin/rulingsAdmin
POST/api/v1/admin/rulings/previewAdmin
POST/api/v1/admin/rulingsAdmin
PATCH/api/v1/admin/rulings/:rulingIdAdmin
DELETE/api/v1/admin/rulings/:rulingIdAdmin