{"openapi":"3.1.0","info":{"title":"RiftScribe Core API","description":"\n## RiftScribe API\n\nRiftScribe is a community toolset for the **Riftbound Trading Card Game**.\n\n### Features\n\n- **Card Database**: Browse and search the complete card collection\n- **Riftboundle**: Daily Wordle-style card guessing game\n\n### Authentication\n\nCurrently, the API is open and does not require authentication.\nGuest users are identified by a session/user ID for game state tracking.\n\n### Rate Limiting\n\nPlease be respectful with API usage. Excessive requests may be rate-limited.\n        ","version":"0.1.0"},"paths":{"/health/liveness":{"get":{"tags":["health"],"summary":"Liveness probe","description":"Check if the application is running. Used by Kubernetes liveness probes.","operationId":"liveness_health_liveness_get","responses":{"200":{"description":"Application is alive","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Liveness Health Liveness Get"}}}}}}},"/health/readiness":{"get":{"tags":["health"],"summary":"Readiness probe","description":"Check if the application is ready to serve traffic. Used by Kubernetes readiness probes.","operationId":"readiness_health_readiness_get","responses":{"200":{"description":"Application is ready","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Readiness Health Readiness Get"}}}}}}},"/api/cards":{"get":{"tags":["cards"],"summary":"List cards","description":"List cards with optional filtering by set, faction, rarity, and name search.","operationId":"list_cards_api_cards_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search query for card name","title":"Q"},"description":"Search query for card name"},{"name":"set_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by set ID","title":"Set Id"},"description":"Filter by set ID"},{"name":"faction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by faction","title":"Faction"},"description":"Filter by faction"},{"name":"rarity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by rarity","title":"Rarity"},"description":"Filter by rarity"},{"name":"is_banned","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by ban status","title":"Is Banned"},"description":"Filter by ban status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of cards to return","default":100,"title":"Limit"},"description":"Maximum number of cards to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of cards to skip","default":0,"title":"Offset"},"description":"Number of cards to skip"}],"responses":{"200":{"description":"List of cards matching the filters","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CardSummaryRead"},"title":"Response List Cards Api Cards Get"},"example":[{"id":"ogn-001-298","name":"Blazing Scorcher","set_id":"OGN","collector_number":1,"rarity":"common","faction":"fury","type":"Unit","stats":{"energy":5,"might":5},"image":"https://cdn.riftscribe.gg/riftscribe/cards/originals/ogn-001-298-8de89b4b8fb3186d.png","image_thumb":{"small":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/small/ogn-001-298-8de89b4b8fb3186d.png","medium":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/medium/ogn-001-298-8de89b4b8fb3186d.png","large":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/large/ogn-001-298-8de89b4b8fb3186d.png"}}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cards/search":{"get":{"tags":["cards"],"summary":"Search cards by name (typeahead)","description":"Search cards by name with fuzzy matching for typeahead/autocomplete functionality.\n\nUses PostgreSQL pg_trgm for efficient fuzzy text search. Results are ordered by:\n1. Exact matches first (case-insensitive)\n2. Prefix matches\n3. Similarity score (fuzzy matches)\n4. Alphabetical by name\n\n**Minimum query length**: 2 characters\n**Maximum results**: 20","operationId":"search_cards_api_cards_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"description":"Search query (minimum 2 characters)","title":"Q"},"description":"Search query (minimum 2 characters)"},{"name":"types","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by card types","title":"Types"},"description":"Filter by card types"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"Maximum results","default":20,"title":"Limit"},"description":"Maximum results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CardSearchResult"},"title":"Response Search Cards Api Cards Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cards/filters":{"get":{"tags":["cards"],"summary":"Get available filter values","description":"Get all available distinct values for filtering cards.\n\nReturns distinct values for:\n- **Sets**: All set IDs in the database\n- **Factions**: All faction values\n- **Rarities**: All rarity levels\n\nUseful for populating filter dropdowns with available options.","operationId":"get_card_filters_api_cards_filters_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardFiltersRead"}}}}}}},"/api/cards/{card_id}":{"get":{"tags":["cards"],"summary":"Get a single card with details","description":"Get detailed information for a single card.\n\n**Supported ID formats:**\n- Full ID: `OGN-001-298` (set-collector_number-internal_id)\n- Short format: `OGN-1` or `OGN-001` (set-collector_number)\n\nThe API will normalize collector numbers, so `OGN-1` and `OGN-001` both work.","operationId":"get_card_api_cards__card_id__get","parameters":[{"name":"card_id","in":"path","required":true,"schema":{"type":"string","title":"Card Id"}}],"responses":{"200":{"description":"Card details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardRead"}}}},"404":{"description":"Card not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/riftboundle/today":{"get":{"tags":["riftboundle"],"summary":"Get today's puzzle","description":"Get or create today's Riftboundle puzzle.\n\nReturns puzzle metadata without revealing the solution.\nPuzzles are generated deterministically based on the date.","operationId":"get_today_puzzle_api_riftboundle_today_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PuzzleMetadata"}}}}}}},"/api/riftboundle/guess":{"post":{"tags":["riftboundle"],"summary":"Submit a guess","description":"Submit a guess for a Riftboundle puzzle.\n\nThe API validates the puzzle, evaluates the guess against the solution,\nand stores the attempt. Returns attribute-by-attribute feedback without\nrevealing the solution unless the guess is correct.\n\n**Evaluation results:**\n- `exact`: The attribute matches exactly\n- `close`: The attribute is close (for numeric values)\n- `wrong`: The attribute does not match","operationId":"submit_puzzle_guess_api_riftboundle_guess_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuessRequest"}}},"required":true},"responses":{"200":{"description":"Guess evaluated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuessResult"}}}},"400":{"description":"Invalid guess (puzzle expired, max attempts reached, etc.)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/riftboundle/state":{"get":{"tags":["riftboundle"],"summary":"Get puzzle state","description":"Get the current state of a puzzle for a user.\n\nReturns all previous attempts and whether the puzzle is solved/finished.\nUseful for refreshing the page or resuming an in-progress game.","operationId":"get_puzzle_state_api_riftboundle_state_get","parameters":[{"name":"puzzle_id","in":"query","required":true,"schema":{"type":"integer","title":"Puzzle Id"}},{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Current puzzle state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PuzzleState"}}}},"404":{"description":"Puzzle not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"CardArtRead":{"properties":{"image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image","description":"Full resolution image URL"},"image_thumb":{"anyOf":[{"$ref":"#/components/schemas/ImageThumbnails"},{"type":"null"}],"description":"Responsive thumbnail URLs"},"artist":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Artist","description":"Artist name"}},"type":"object","title":"CardArtRead","description":"Card artwork and artist information."},"CardFiltersRead":{"properties":{"sets":{"items":{"type":"string"},"type":"array","title":"Sets","description":"Available set IDs"},"factions":{"items":{"type":"string"},"type":"array","title":"Factions","description":"Available factions"},"rarities":{"items":{"type":"string"},"type":"array","title":"Rarities","description":"Available rarities"}},"type":"object","title":"CardFiltersRead","description":"Available filter options for card searches.","example":{"factions":["Fire","Water","Earth","Air"],"rarities":["Common","Uncommon","Rare","Legendary"],"sets":["OGN","EXP"]}},"CardRead":{"properties":{"id":{"type":"string","title":"Id","description":"Unique card identifier (format: SET-NUMBER-ID)"},"name":{"type":"string","title":"Name","description":"Card name"},"set_id":{"type":"string","title":"Set Id","description":"Set identifier (e.g., 'OGN' for Origins)"},"collector_number":{"type":"integer","title":"Collector Number","description":"Collector number within the set"},"rarity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rarity","description":"Card rarity (Common, Uncommon, Rare, etc.)"},"faction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Faction","description":"Card faction"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Card type (Creature, Spell, etc.)"},"orientation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orientation","description":"Card orientation: 'portrait' or 'landscape'"},"stats":{"anyOf":[{"$ref":"#/components/schemas/CardStatsRead"},{"type":"null"}],"description":"Card stats"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image","description":"Full resolution image URL"},"image_thumb":{"anyOf":[{"$ref":"#/components/schemas/ImageThumbnails"},{"type":"null"}],"description":"Responsive thumbnail URLs"},"image_blur_data_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Blur Data Url","description":"Tiny base64 JPEG data URL for progressive blur placeholder"},"is_banned":{"type":"boolean","title":"Is Banned","description":"Whether the card is banned from competitive play","default":false},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Card ability text"},"flavor_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flavor Text","description":"Flavor text"},"art":{"anyOf":[{"$ref":"#/components/schemas/CardArtRead"},{"type":"null"}],"description":"Card artwork details"},"keywords":{"items":{"type":"string"},"type":"array","title":"Keywords","description":"Card keywords (e.g., 'Flying', 'Haste')"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Card tags for categorization"},"prev_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prev Card Id","description":"Previous card ID in the same set by collector number"},"next_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Card Id","description":"Next card ID in the same set by collector number"}},"type":"object","required":["id","name","set_id","collector_number"],"title":"CardRead","description":"Full card details including stats, art, and metadata.","example":{"collector_number":1,"faction":"fury","id":"ogn-001-298","image":"https://cdn.riftscribe.gg/riftscribe/cards/originals/ogn-001-298-8de89b4b8fb3186d.png","image_thumb":{"large":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/large/ogn-001-298-8de89b4b8fb3186d.png","medium":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/medium/ogn-001-298-8de89b4b8fb3186d.png","small":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/small/ogn-001-298-8de89b4b8fb3186d.png"},"name":"Blazing Scorcher","rarity":"common","set_id":"OGN","stats":{"energy":5,"might":5},"type":"Unit"}},"CardSearchResult":{"properties":{"card_id":{"type":"string","title":"Card Id","description":"Unique card identifier"},"name":{"type":"string","title":"Name","description":"Card name"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Card type"},"set_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Set Id","description":"Set identifier"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url","description":"Small thumbnail URL"},"is_banned":{"type":"boolean","title":"Is Banned","description":"Whether the card is banned from competitive play","default":false}},"type":"object","required":["card_id","name"],"title":"CardSearchResult","description":"Card search result for typeahead/autocomplete.","example":{"card_id":"OGN-001-298","name":"Flame Guardian","set_id":"OGN","thumbnail_url":"https://cdn.riftscribe.com/cards/ogn/001_150.webp","type":"Creature"}},"CardStatsRead":{"properties":{"energy":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Energy","description":"Energy cost to play the card"},"might":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Might","description":"Might stat for the card"},"power":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Power","description":"Power stat for the card"}},"type":"object","title":"CardStatsRead","description":"Combat and resource stats for a card."},"CardSummaryRead":{"properties":{"id":{"type":"string","title":"Id","description":"Unique card identifier (format: SET-NUMBER-ID)"},"name":{"type":"string","title":"Name","description":"Card name"},"set_id":{"type":"string","title":"Set Id","description":"Set identifier (e.g., 'OGN' for Origins)"},"collector_number":{"type":"integer","title":"Collector Number","description":"Collector number within the set"},"rarity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rarity","description":"Card rarity (Common, Uncommon, Rare, etc.)"},"faction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Faction","description":"Card faction"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Card type (Creature, Spell, etc.)"},"orientation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orientation","description":"Card orientation: 'portrait' or 'landscape'"},"stats":{"anyOf":[{"$ref":"#/components/schemas/CardStatsRead"},{"type":"null"}],"description":"Card stats (energy, might, power)"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image","description":"Full resolution image URL"},"image_thumb":{"anyOf":[{"$ref":"#/components/schemas/ImageThumbnails"},{"type":"null"}],"description":"Responsive thumbnail URLs"},"image_blur_data_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Blur Data Url","description":"Tiny base64 JPEG data URL for progressive blur placeholder"},"is_banned":{"type":"boolean","title":"Is Banned","description":"Whether the card is banned from competitive play","default":false}},"type":"object","required":["id","name","set_id","collector_number"],"title":"CardSummaryRead","description":"Summary card information for list views.","example":{"collector_number":1,"faction":"fury","id":"ogn-001-298","image":"https://cdn.riftscribe.gg/riftscribe/cards/originals/ogn-001-298-8de89b4b8fb3186d.png","image_thumb":{"large":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/large/ogn-001-298-8de89b4b8fb3186d.png","medium":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/medium/ogn-001-298-8de89b4b8fb3186d.png","small":"https://cdn.riftscribe.gg/riftscribe/cards/thumbnails/small/ogn-001-298-8de89b4b8fb3186d.png"},"name":"Blazing Scorcher","rarity":"common","set_id":"OGN","stats":{"energy":5,"might":5},"type":"Unit"}},"EvaluationEntry":{"properties":{"guess":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Guess","description":"The guessed value"},"actual":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Actual","description":"The actual value (hidden if not solved)"},"result":{"type":"string","title":"Result","description":"Match result: 'exact', 'close', or 'wrong'"}},"type":"object","required":["result"],"title":"EvaluationEntry","description":"Evaluation result for a single card attribute.","example":{"guess":"Fire","result":"exact"}},"GuessRequest":{"properties":{"puzzle_id":{"type":"integer","title":"Puzzle Id","description":"ID of the puzzle being played"},"guess_card_id":{"type":"string","title":"Guess Card Id","description":"ID of the card being guessed"},"user_id":{"type":"string","title":"User Id","description":"User ID or session ID for guest users"}},"type":"object","required":["puzzle_id","guess_card_id","user_id"],"title":"GuessRequest","description":"Request to submit a guess for a Riftboundle puzzle.","example":{"guess_card_id":"OGN-001-298","puzzle_id":42,"user_id":"guest-abc123"}},"GuessResult":{"properties":{"attempt_no":{"type":"integer","title":"Attempt No","description":"Attempt number (1-based)"},"guess_card_id":{"type":"string","title":"Guess Card Id","description":"ID of the guessed card"},"guess_card_name":{"type":"string","title":"Guess Card Name","description":"Name of the guessed card"},"guess_card_thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guess Card Thumbnail","description":"Thumbnail URL of guessed card"},"evaluation":{"additionalProperties":{"$ref":"#/components/schemas/EvaluationEntry"},"type":"object","title":"Evaluation","description":"Attribute-by-attribute evaluation results"},"is_solved":{"type":"boolean","title":"Is Solved","description":"True if this guess was correct"},"is_finished":{"type":"boolean","title":"Is Finished","description":"True if puzzle is complete (solved or max attempts reached)"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp of the guess"},"guess_card_is_banned":{"type":"boolean","title":"Guess Card Is Banned","description":"True if the guessed card is banned","default":false}},"type":"object","required":["attempt_no","guess_card_id","guess_card_name","evaluation","is_solved","is_finished","created_at"],"title":"GuessResult","description":"Result of a guess evaluation."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImageThumbnails":{"properties":{"small":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Small","description":"Small thumbnail URL (150px)"},"medium":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Medium","description":"Medium thumbnail URL (300px)"},"large":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Large","description":"Large thumbnail URL (600px)"}},"type":"object","title":"ImageThumbnails","description":"Responsive image thumbnails at different resolutions."},"PuzzleMetadata":{"properties":{"puzzle_id":{"type":"integer","title":"Puzzle Id","description":"Unique puzzle identifier"},"date":{"type":"string","format":"date-time","title":"Date","description":"Puzzle date (UTC)"},"max_attempts":{"type":"integer","title":"Max Attempts","description":"Maximum allowed guesses"},"ruleset_version":{"type":"integer","title":"Ruleset Version","description":"Game ruleset version"}},"type":"object","required":["puzzle_id","date","max_attempts","ruleset_version"],"title":"PuzzleMetadata","description":"Metadata about a Riftboundle puzzle (does not include solution).","example":{"date":"2026-02-11T00:00:00Z","max_attempts":6,"puzzle_id":42,"ruleset_version":1}},"PuzzleState":{"properties":{"puzzle_id":{"type":"integer","title":"Puzzle Id","description":"Unique puzzle identifier"},"date":{"type":"string","format":"date-time","title":"Date","description":"Puzzle date (UTC)"},"attempts":{"items":{"$ref":"#/components/schemas/GuessResult"},"type":"array","title":"Attempts","description":"List of previous guess attempts"},"is_solved":{"type":"boolean","title":"Is Solved","description":"True if puzzle has been solved"},"is_finished":{"type":"boolean","title":"Is Finished","description":"True if puzzle is complete"},"max_attempts":{"type":"integer","title":"Max Attempts","description":"Maximum allowed guesses"}},"type":"object","required":["puzzle_id","date","attempts","is_solved","is_finished","max_attempts"],"title":"PuzzleState","description":"Current state of a puzzle for a user, including all previous attempts."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"tags":[{"name":"cards","description":"Card database operations - browse, search, and retrieve card details"},{"name":"riftboundle","description":"Riftboundle daily puzzle game - guess the mystery card"},{"name":"health","description":"Health check endpoints for monitoring and orchestration"}]}