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"
}
| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameter |
401 | Unauthenticated — missing or invalid token (auth routes only) |
404 | Resource not found |
500 | Internal 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/:
| Module | Routes |
|---|---|
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
- Add the handler to the relevant route module in
src/routes/ - Annotate it with Elysia schema types (
.query(),.body(),.response()) and adetailblock (used for Eden Treaty types and static spec generation) - Write a test in
src/__tests__/routes/ - Update the relevant doc page in
apps/api/docs/ - If the endpoint stores or exposes new personal data, review
apps/web/src/views/privacy-view.tsx
Endpoints
| Method | Path | Doc |
|---|---|---|
GET | /api/v1/health | Meta |
GET | /api/v1/meta | Meta |
GET | /api/v1/cards | Search |
GET | /api/v1/cards/random | Cards |
GET | /api/v1/cards/detail | Cards |
GET | /api/v1/cards/:id | Cards |
GET | /api/v1/cards/:id/text | Cards |
GET | /api/v1/cards/by-slug/* | Cards |
POST | /api/v1/cards/resolve | Cards |
GET | /api/v1/printings/:id | Cards |
GET | /api/v1/sets | Sets |
GET | /api/v1/formats | Formats |
GET | /api/v1/decks | Decks |
POST | /api/v1/decks | Decks |
GET | /api/v1/decks/:id | Decks |
PATCH | /api/v1/decks/:id | Decks |
DELETE | /api/v1/decks/:id | Decks |
PUT | /api/v1/decks/:id/cards | Decks |
GET | /api/v1/decks/:id/revisions | Decks |
POST | /api/v1/decks/:id/invite | Decks |
DELETE | /api/v1/decks/:id/invite | Decks |
POST | /api/v1/decks/join/:code | Decks |
POST | /api/v1/decks/:id/collaborators | Decks |
DELETE | /api/v1/decks/:id/collaborators | Decks |
POST | /api/v1/decks/import | Decks |
GET | /api/v1/decks/:id/export | Decks |
POST | /api/v1/auth/register | Auth |
POST | /api/v1/auth/login | Auth |
POST | /api/v1/auth/refresh | Auth |
POST | /api/v1/auth/logout | Auth |
POST | /api/v1/auth/forgot-password | Auth |
GET | /api/v1/auth/me | Auth |
POST | /api/v1/auth/reset-password | Auth |
GET | /api/v1/admin/audit-log | Admin |
GET | /api/v1/admin/reconciliation | Admin |
POST | /api/v1/admin/reconciliation/:id/confirm | Admin |
POST | /api/v1/admin/reconciliation/:id/dismiss | Admin |
POST | /api/v1/admin/oracles | Admin |
PATCH | /api/v1/admin/oracles/:id | Admin |
DELETE | /api/v1/admin/oracles/:id | Admin |
POST | /api/v1/admin/oracles/:id/restore | Admin |
GET | /api/v1/admin/oracles/:id/relationships | Admin |
PUT | /api/v1/admin/oracles/:id/relationships | Admin |
POST | /api/v1/admin/printings | Admin |
PATCH | /api/v1/admin/printings/:id | Admin |
DELETE | /api/v1/admin/printings/:id | Admin |
POST | /api/v1/admin/printings/:id/restore | Admin |
POST | /api/v1/admin/printings/:id/regenerate-slug | Admin |
GET | /api/v1/admin/printings/:id/deltas | Admin |
PUT | /api/v1/admin/printings/:id/deltas | Admin |
POST | /api/v1/admin/printings/:id/image | Admin |
GET | /api/v1/admin/printings/:id/legalities | Admin |
PUT | /api/v1/admin/printings/:id/legalities | Admin |
GET | /api/v1/admin/printings/:id/rulings | Admin |
POST | /api/v1/admin/sets | Admin |
PATCH | /api/v1/admin/sets/:setCode | Admin |
DELETE | /api/v1/admin/sets/:setCode | Admin |
GET | /api/v1/admin/formats | Admin |
POST | /api/v1/admin/formats | Admin |
PUT | /api/v1/admin/formats/order | Admin |
PATCH | /api/v1/admin/formats/:code | Admin |
DELETE | /api/v1/admin/formats/:code | Admin |
PUT | /api/v1/admin/formats/:code/zone-rules/:zone | Admin |
DELETE | /api/v1/admin/formats/:code/zone-rules/:zone | Admin |
PUT | /api/v1/admin/formats/:code/severities/:legality_status | Admin |
GET | /api/v1/admin/rulings | Admin |
POST | /api/v1/admin/rulings/preview | Admin |
POST | /api/v1/admin/rulings | Admin |
PATCH | /api/v1/admin/rulings/:rulingId | Admin |
DELETE | /api/v1/admin/rulings/:rulingId | Admin |