Trivia API for game developers
Quiz mechanic for Unity, Roblox, React Native. Multilingual out of the box.
TL;DR
- Drop-in quiz mechanic for your game — React Native + Expo primary, Unity / Godot / Roblox / web variants. Same REST API across all five.
- Twenty lines of code for the data layer. Your game engine handles rendering, input, sound — you bring trivia content.
- Fifty thousand-plus curated questions in twenty-plus languages out of the box. Filter by category, difficulty, or topic.
- Free tier — 500 requests per day, no credit card. Plus an offline BY-SA dump for mobile bundles where you cannot rely on network at runtime.
Why this exists
You are shipping a game. Maybe a mobile word puzzle, a Roblox class field-trip experience, a Unity edu game for tablets, a Twitch stream trivia overlay. The mechanic is universal — present a question, four choices, score a tap, advance round. The hard part is not the rendering. The hard part is sourcing fifty thousand consistent, deduplicated, multi-language trivia questions that will not embarrass you when a player screenshots a wrong answer and posts it to your subreddit.
Today the options are bad. Hand-curating questions burns a week of designer time before the first build. LLM-generating questions produces hallucinated answers, inconsistent difficulty, and ongoing token bills that scale with daily active users — fine for a prototype, broken for a shipping game. Scraping Open Trivia DB gets you a small English-only dataset that has not been actively maintained in years. Hiring trivia writers is a real budget line item before you have proven the game loop is fun.
QuizBase is the question pool you want behind the trivia mechanic. Fifty thousand-plus curated questions in twenty-plus languages, per-row attribution so commercial use is unambiguous, offline BY-SA dump for mobile bundles. One REST endpoint, one publishable key in your game build, twenty lines of integration code. Your engine — Expo, Unity, Godot, Roblox, vanilla web — handles the UX. We handle the dataset.
What you will build
A working quiz mechanic in your game runtime. Fetch a question from QuizBase, render it with your engine's native components (`<View>` and `<Text>` in React Native, `Label` and `Button` nodes in Godot, `TextLabel` and `TextButton` in Roblox, `GameObject` with `TextMeshProUGUI` in Unity), tap to answer, animated feedback for correct/wrong, advance to the next question. Score tracking is a couple of state variables.
The primary walkthrough uses React Native + Expo because it is the most accessible cross-platform stack for solo game developers — one codebase ships to iOS, Android, and web with a single `pnpm` install. If you live in Unity 6 LTS, Godot 4.3, or Roblox Studio, jump to the stack variants section — the REST call is identical, only the rendering layer changes.
For mobile games where players might be offline (subway, airplane, classroom field trip), you have two paths: pre-fetch a batch with `?limit=50` and cache in state, or bundle the BY-SA dump from `/data` into your app and serve questions from disk. The dump is updated weekly, attribution is per-row, and the license (CC BY-SA 4.0) is compatible with commercial games as long as you ship the attribution screen.
The stack — Expo first, engine variants below
We choose **React Native + Expo SDK 54+** as the primary stack because it is the broadest indie audience and the friendliest tooling for solo developers — `npx create-expo-app` to running on a phone takes about three minutes. The QuizBase API call is identical to any other REST API your game might consume (fetch + JSON), which means the integration burden is genuinely a one-screen tutorial, not a multi-day SDK setup.
For game engines that are not React Native, the QuizBase call is the same shape with slightly different syntax — `UnityWebRequest` in Unity, `HTTPRequest` node in Godot, `HttpService:RequestAsync` in Roblox. Stack variants below have copy-paste code for each. The key thing to remember: **publishable keys** (prefix `qb_pk_…`) are designed for client tools (mobile apps, game clients), no server proxy required for the free tier.
- React Native + Expo SDK 54+ —
npx create-expo-app, one codebase for iOS / Android / web. Primary walkthrough. - QuizBase REST API — one endpoint (
GET /api/v1/questions/random), one header (X-API-Key), JSON response withtext,correctAnswer,incorrectAnswers. -
expo-audio(SDK 53+ — replaces deprecatedexpo-av) — drop-in sound effects viauseAudioPlayerhook. Two lines per sound. Optional but adds polish in five minutes. - No backend required — publishable keys are CORS-safe and client-bundle-safe. Server proxy is optional, only useful if you want to hide your usage patterns or layer caching.
- Offline-capable — BY-SA dump at
/data(JSON files, weekly refresh) bundles inassets/for mobile games where network at runtime is unreliable.
Build the quiz mechanic step-by-step
Seven steps, twenty minutes. The first six get you a playable quiz screen on your phone; the seventh adds optional multilang. Each step is one paragraph of context plus copy-paste code. If your stack is Unity / Godot / Roblox, jump to the stack variants section after step 2 — the REST call is identical, only the rendering layer changes.
Step 1
Create the Expo projectSpin up a fresh Expo app. The `default` template gives you React Native, TypeScript, file-based routing (Expo Router), and the Expo SDK preinstalled. You can run it on a real phone via the Expo Go app, on an iOS simulator, on an Android emulator, or in the browser — all from the same command.
npx create-expo-app@latest quiz-game --template default cd quiz-game pnpm installStep 2
Get your free QuizBase API keySign up at quizbase.runriva.com/pricing — pick the free tier (no card, 500 requests per day, every endpoint unlocked). Pick a **publishable key** (prefix `qb_pk_…`, dashboard scope label `publishable`) — publishable keys are designed for client-side use including game clients. Copy it once (shown in plaintext exactly once), then add it to a new `.env.local` file. Expo exposes any env var prefixed with `EXPO_PUBLIC_` to the client bundle at build time.
.env.local (Expo reads this automatically) EXPO_PUBLIC_QUIZBASE_KEY=qb_pk_your_key_hereStep 3
Fetch a question and render itOpen `app/index.tsx` and replace its contents. A single `useEffect` fires once on mount, fetches one random question from QuizBase, and renders it as a `<Text>` heading. The response shape is `{ data: [{ id, text, correctAnswer, incorrectAnswers }] }` — `correctAnswer` and `incorrectAnswers` come back as separate fields so you can decide whether to shuffle (you should, otherwise the correct answer is always last and your game is solved).
app/index.tsx — fetch and render import { useEffect, useState } from 'react'; import { Text, View } from 'react-native'; interface Question { id: string; text: string; correctAnswer: string; incorrectAnswers: string[]; } export default function HomeScreen() { const [q, setQ] = useState<Question | null>(null); useEffect(() => { fetch( 'https://quizbase.runriva.com/api/v1/questions/random' + '?lang=en&limit=1', { headers: { 'X-API-Key': process.env.EXPO_PUBLIC_QUIZBASE_KEY! } } ) .then((r) => r.json()) .then((d) => setQ(d.data[0])); }, []); if (!q) return <Text>Loading…</Text>; return ( <View style={{ flex: 1, padding: 24, paddingTop: 80 }}> <Text style={{ fontSize: 22, fontWeight: '600' }}>{q.text}</Text> </View> ); }Step 4
Render the four answer choices as Pressable buttonsMix `correctAnswer` with the three `incorrectAnswers`, shuffle once per question (a `useMemo` keyed on `question.id` so the order does not change between re-renders), render as `Pressable` components. Pressable is the React Native primitive for tappable areas — it handles touch targets, ripple effects on Android, and visual feedback on iOS out of the box.
Add to the component import { useEffect, useMemo, useState } from 'react'; import { Pressable, Text, View } from 'react-native'; // … const choices = useMemo(() => { if (!q) return []; return [q.correctAnswer, ...q.incorrectAnswers] .map((value) => ({ value, sort: Math.random() })) .sort((a, b) => a.sort - b.sort) .map(({ value }) => value); }, [q?.id]); return ( <View style={{ flex: 1, padding: 24, paddingTop: 80 }}> <Text style={{ fontSize: 22, fontWeight: '600' }}>{q.text}</Text> {choices.map((c) => ( <Pressable key={c} style={{ marginTop: 12, padding: 16, backgroundColor: '#eee', borderRadius: 8 }} > <Text style={{ fontSize: 16 }}>{c}</Text> </Pressable> ))} </View> );Step 5
Handle answer taps with instant feedbackTrack which choice the user tapped. Compare to `q.correctAnswer`. Style the tapped button green if correct, red if wrong. Disable all buttons after one is tapped — players should not be able to change their answer once committed (that is how trivia games stop feeling fair). This is the smallest amount of state that turns the screen into a game.
Replace the Pressable map const [selected, setSelected] = useState<string | null>(null); {choices.map((c) => { const isThis = selected === c; const isCorrect = c === q.correctAnswer; return ( <Pressable key={c} disabled={selected !== null} onPress={() => setSelected(c)} style={{ marginTop: 12, padding: 16, backgroundColor: isThis ? isCorrect ? '#c8f7c5' : '#f7c5c5' : '#eee', borderRadius: 8 }} > <Text style={{ fontSize: 16 }}>{c}</Text> </Pressable> ); })} {selected && ( <Text style={{ marginTop: 16, fontSize: 16 }}> {selected === q.correctAnswer ? '✓ Correct!' : `✗ Wrong — the answer was ${q.correctAnswer}.`} </Text> )}Step 6
Add sound effects with expo-audioTrivia games feel three times better with sound. `expo-audio` (SDK 53+ — replaced the deprecated `expo-av`) ships with the default Expo template. Install once if missing: `npx expo install expo-audio`. Two royalty-free MP3s (correct ding, wrong buzzer) live in `assets/sounds/` — Freesound, ZapSplat, or your own GarageBand session. The `useAudioPlayer` hook returns a player object; call `player.play()` after each answer.
Add to the component import { useAudioPlayer } from 'expo-audio'; // Top of component — useAudioPlayer is a hook, must be at top level const correctPlayer = useAudioPlayer(require('../assets/sounds/correct.mp3')); const wrongPlayer = useAudioPlayer(require('../assets/sounds/wrong.mp3')); useEffect(() => { if (!selected) return; const player = selected === q.correctAnswer ? correctPlayer : wrongPlayer; player.seekTo(0); player.play(); }, [selected]);Step 7
Add a multilang switcher (optional)Players love seeing their native language in a trivia game. Adding language support is one extra state variable plus `?lang=` on the fetch. Pick from `en` (English), `pl` (Polish), `es` (Spanish), `de` (German), `fr` (French), `it` (Italian), `ja` (Japanese), `pt` (Portuguese), and a dozen more — full list at `GET /api/v1/languages`. Per-language question counts vary (English has the most coverage, smaller languages have fewer questions per category) so combining `?lang=` with a narrow `?category=` filter may return fewer results than you expect — pre-flight with `quizbase_languages` if you build a language picker.
Add language picker const [lang, setLang] = useState<'en' | 'pl' | 'es' | 'de'>('en'); // in fetch URL: `https://quizbase.runriva.com/api/v1/questions/random?lang=${lang}&limit=1` // in JSX, above question: <View style={{ flexDirection: 'row', gap: 8 }}> {(['en', 'pl', 'es', 'de'] as const).map((l) => ( <Pressable key={l} onPress={() => { setLang(l); setSelected(null); }} style={{ padding: 8 }} > <Text style={{ fontWeight: lang === l ? 'bold' : 'normal' }}> {l.toUpperCase()} </Text> </Pressable> ))} </View>
The complete app — one file, copy-paste ready
The full Expo quiz screen with language picker, score tracker, sound effects, and Next button. Drop it in `app/index.tsx`, add `EXPO_PUBLIC_QUIZBASE_KEY` to `.env.local`, drop two MP3s in `assets/sounds/`, run `pnpm start`.
import { useAudioPlayer } from 'expo-audio';
import { useEffect, useMemo, useState } from 'react';
import { Pressable, Text, View } from 'react-native';
interface Question {
id: string;
text: string;
correctAnswer: string;
incorrectAnswers: string[];
}
const API = 'https://quizbase.runriva.com/api/v1/questions/random';
const KEY = process.env.EXPO_PUBLIC_QUIZBASE_KEY!;
async function fetchQuestion(lang: string): Promise<Question> {
const r = await fetch(`${API}?lang=${lang}&limit=1`, {
headers: { 'X-API-Key': KEY }
});
const d = await r.json();
return d.data[0];
}
export default function HomeScreen() {
const [q, setQ] = useState<Question | null>(null);
const [selected, setSelected] = useState<string | null>(null);
const [lang, setLang] = useState<'en' | 'pl' | 'es' | 'de'>('en');
const [score, setScore] = useState({ right: 0, wrong: 0 });
// expo-audio: hooks at top level, replay via seekTo(0) + play()
const correctPlayer = useAudioPlayer(require('../assets/sounds/correct.mp3'));
const wrongPlayer = useAudioPlayer(require('../assets/sounds/wrong.mp3'));
useEffect(() => { void fetchQuestion(lang).then(setQ); }, [lang]);
useEffect(() => {
if (!selected || !q) return;
const correct = selected === q.correctAnswer;
setScore((s) => correct ? { ...s, right: s.right + 1 } : { ...s, wrong: s.wrong + 1 });
const player = correct ? correctPlayer : wrongPlayer;
player.seekTo(0);
player.play();
}, [selected]);
const choices = useMemo(() => {
if (!q) return [];
return [q.correctAnswer, ...q.incorrectAnswers]
.map((value) => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
}, [q?.id]);
if (!q) return <Text style={{ padding: 24 }}>Loading…</Text>;
return (
<View style={{ flex: 1, padding: 24, paddingTop: 80 }}>
<View style={{ flexDirection: 'row', gap: 12, marginBottom: 16 }}>
{(['en', 'pl', 'es', 'de'] as const).map((l) => (
<Pressable key={l} onPress={() => { setLang(l); setSelected(null); }}>
<Text style={{ fontWeight: lang === l ? 'bold' : 'normal' }}>{l.toUpperCase()}</Text>
</Pressable>
))}
<Text style={{ marginLeft: 'auto' }}>
{score.right}/{score.right + score.wrong}
</Text>
</View>
<Text style={{ fontSize: 22, fontWeight: '600' }}>{q.text}</Text>
{choices.map((c) => {
const isThis = selected === c;
const isCorrect = c === q.correctAnswer;
return (
<Pressable
key={c}
disabled={selected !== null}
onPress={() => setSelected(c)}
style={{
marginTop: 12, padding: 16,
backgroundColor: isThis
? isCorrect ? '#c8f7c5' : '#f7c5c5'
: '#eee',
borderRadius: 8
}}
>
<Text style={{ fontSize: 16 }}>{c}</Text>
</Pressable>
);
})}
{selected && (
<Pressable
onPress={() => { setSelected(null); void fetchQuestion(lang).then(setQ); }}
style={{ marginTop: 16, padding: 16, backgroundColor: '#222', borderRadius: 8 }}
>
<Text style={{ color: '#fff', fontSize: 16, textAlign: 'center' }}>Next question</Text>
</Pressable>
)}
</View>
);
}Get your free API key at /pricing — no credit card.
Let the AI write it — Cursor, Claude Code, Copilot
You are a game dev with a weekend prototype to ship. Of course you want the AI to write the data layer. Here are three prompts you can paste into Cursor, Claude Code, or GitHub Copilot — each produces a working quiz screen in under two minutes. The prompts include the exact API response shape so the AI does not hallucinate `question.choices` instead of `question.incorrectAnswers`.
Cursor
Cursor is the fastest path. The composer (Cmd+I on macOS, Ctrl+I on Windows/Linux) takes one prompt and rewrites your app/index.tsx in one shot. Open your fresh Expo project, hit the shortcut, paste the prompt. In under two minutes you have a tappable quiz on your phone via Expo Go.
How to use: Open the project root in Cursor → Cmd+I (Ctrl+I) → paste prompt → Enter → review the proposed edit → Accept → run `pnpm start` and scan the QR with Expo Go.
Build an Expo (React Native) trivia quiz game using the QuizBase API.
Stack: Expo SDK 54+ + TypeScript + Expo Router (default template). Single screen in app/index.tsx.
API call:
GET https://quizbase.runriva.com/api/v1/questions/random?lang=en&limit=1
Header: X-API-Key: <process.env.EXPO_PUBLIC_QUIZBASE_KEY>
Response shape:
{
"data": [{
"id": "uuid",
"text": "What is...?",
"correctAnswer": "Answer",
"incorrectAnswers": ["Wrong 1", "Wrong 2", "Wrong 3"]
}]
}
Requirements:
1. Fetch one question on mount + on language change.
2. Render question and four answer choices as <Pressable> buttons (shuffle correct + incorrect with useMemo keyed on question.id).
3. Tap a choice → highlight it green (correct) or red (wrong), disable all buttons, show feedback.
4. Score tracker (right/total) in the top-right corner.
5. Language picker for en/pl/es/de — refetches on switch.
6. "Next question" button at the bottom after answering.
7. Sound effects on answer — use `expo-audio` (SDK 53+, replaces deprecated `expo-av`): import { useAudioPlayer } from 'expo-audio', call hook at top level for both correct.mp3 and wrong.mp3 in assets/sounds/, then player.seekTo(0) + player.play() in the answer effect.
8. Loading state ("Loading…") before first question arrives.
Use TypeScript types for the Question shape. Use React Native primitives only (View, Text, Pressable). One extra dep: `expo-audio` (npx expo install expo-audio).Claude Code
Claude Code runs in your terminal and edits files directly. If you live in a terminal split (one pane your editor, one pane Claude), this is the move. Same prompt, different invocation — Claude proposes file edits one at a time and waits for your accept/reject on each.
How to use: From the project root: `claude` then paste the prompt. Claude reads the existing app/index.tsx, proposes the rewrite, and asks 'Accept this edit?'. Say yes.
Build an Expo (React Native) trivia quiz game using the QuizBase API.
Stack: Expo SDK 54+ + TypeScript + Expo Router (default template). Single screen in app/index.tsx.
API call:
GET https://quizbase.runriva.com/api/v1/questions/random?lang=en&limit=1
Header: X-API-Key: <process.env.EXPO_PUBLIC_QUIZBASE_KEY>
Response shape:
{
"data": [{
"id": "uuid",
"text": "What is...?",
"correctAnswer": "Answer",
"incorrectAnswers": ["Wrong 1", "Wrong 2", "Wrong 3"]
}]
}
Requirements:
1. Fetch one question on mount + on language change.
2. Render question and four answer choices as <Pressable> buttons (shuffle correct + incorrect with useMemo keyed on question.id).
3. Tap a choice → highlight it green (correct) or red (wrong), disable all buttons, show feedback.
4. Score tracker (right/total) in the top-right corner.
5. Language picker for en/pl/es/de — refetches on switch.
6. "Next question" button at the bottom after answering.
7. Sound effects on answer — use `expo-audio` (SDK 53+, replaces deprecated `expo-av`): import { useAudioPlayer } from 'expo-audio', call hook at top level for both correct.mp3 and wrong.mp3 in assets/sounds/, then player.seekTo(0) + player.play() in the answer effect.
8. Loading state ("Loading…") before first question arrives.
Use TypeScript types for the Question shape. Use React Native primitives only (View, Text, Pressable). One extra dep: `expo-audio` (npx expo install expo-audio).GitHub Copilot
Copilot is comment-driven. You write the spec as a top-of-file comment block, leave a TypeScript scaffold, and press Tab on the empty function body. Copilot fills in. Best when you want to learn by reading what an LLM writes line-by-line — it suggests one statement at a time, so you see each decision.
How to use: Paste the comment block + scaffold below into a fresh `app/index.tsx`, place your cursor inside the empty function body, and press Tab. Accept each suggestion with Tab, dismiss with Esc.
// Expo trivia quiz screen powered by the QuizBase API
// API: GET https://quizbase.runriva.com/api/v1/questions/random?lang=en&limit=1
// Auth: X-API-Key header from process.env.EXPO_PUBLIC_QUIZBASE_KEY
// Response shape: { data: [{ id, text, correctAnswer, incorrectAnswers: string[] }] }
//
// Requirements:
// - Fetch question on mount with useEffect
// - Shuffle correct + incorrect into four <Pressable> buttons (useMemo keyed on question.id)
// - On tap: highlight green/red, disable all, show feedback text
// - "Next question" button refetches and resets
// - Score tracker (right/total)
// - Language picker for en/pl/es/de
// - Sound effects via expo-audio (useAudioPlayer hook, NOT deprecated expo-av)
import { useEffect, useMemo, useState } from 'react';
import { Pressable, Text, View } from 'react-native';
interface Question {
id: string;
text: string;
correctAnswer: string;
incorrectAnswers: string[];
}
export default function HomeScreen() {
// Press Tab and let Copilot write the rest
}MCP — design-time only (skip if you are shipping)
For game devs, MCP is mostly a design-time tool — your Cursor or Claude Code agent gets QuizBase tools as native capabilities while you build the game, but the shipping game still uses REST at runtime (faster, smaller bundle, simpler debugging). If you are building an agent that wraps a game (Twitch trivia bot, Discord Activity, Custom GPT), see `/use-cases/ai-agent-builders` for the full MCP setup.
- For game devs, MCP is mostly a design-time tool — your Cursor or Claude Code agent gets
quizbase_random,quizbase_categoriesas native capabilities while you build the game. The shipping game still calls REST at runtime (faster, simpler, no MCP client to bundle into your mobile build). - Full MCP details (eleven tools, three resources, three prompts) live on the AI Agent Builders page. This section gives you the design-time setup; switch to that page if you are building an agent that uses trivia as a knowledge layer.
Cursor (design-time)
Drop `.cursor/mcp.json` in your Expo project root. Now when you ask Cursor's composer to scaffold a new feature ("add a daily challenge mode", "add Roblox export"), it can call `quizbase_categories` first to ground its work in real data — no schema-teaching prompt.
{
"mcpServers": {
"quizbase": {
"url": "https://quizbase.runriva.com/mcp",
"headers": {
"Authorization": "Bearer qb_pk_your_key_here"
}
}
}
}Try it: In Cursor composer: "Add a kids mode to the quiz game — filter to ?category=animals,sports and ?difficulty=easy. Use quizbase_categories first to confirm those slugs exist." Cursor calls the tool, then writes the filter logic.
Claude Code CLI (design-time)
Claude Code at the terminal. Same value prop as Cursor — your agent can grok the dataset before scaffolding game features. Best when your game dev workflow is terminal-resident (vim/emacs/neovim + Claude in another pane).
claude mcp add quizbase \
--transport http \
--url https://quizbase.runriva.com/mcp \
--header "Authorization: Bearer qb_pk_your_key_here"
# verify
claude mcp listTry it: In Claude Code: "Add a Roblox export — a Lua module that calls quizbase_random with the same filters as the React Native version, returns the response shape Roblox developers expect." Claude reads the tool catalog, plans the script, writes the .lua file.
Runtime MCP — almost never the right call for a shipping game
You **can** technically embed an MCP client in your shipping game (an MCP TypeScript client compiles into Expo, a custom HTTP-based wrapper works in Unity / Godot / Roblox). You almost never should. REST at runtime is faster (no `tools/list` round-trip per session), smaller (no SDK to bundle), simpler (no protocol layer to debug on a real device). MCP shines for agents that need introspection; shipping games already know what tool they need.
Use REST at runtime in your shipping game.
Use MCP at design time in Cursor / Claude Code to scaffold features.
Use MCP at runtime only if you are building an AI agent that wraps the game (Twitch trivia bot, Discord activity bot, ChatGPT Custom GPT).Try it: If you are building one of those agent-wrapping use cases, jump to /use-cases/ai-agent-builders — that page is the MCP-first tutorial with four client setups and a build-your-own-wrapper guide.
Or: ask your AI what to build
Honestly? Your AI has better game ideas than us. The full documentation bundle (every endpoint, every parameter, every filter) lives as a single document at `https://quizbase.runriva.com/llms-full.txt` — one fetch, no scraping. Paste the prompt below into Cursor, Claude Code, or ChatGPT. It will read the docs, brainstorm game mechanics, and you can prototype something we never imagined.
I am building a game and I want to use QuizBase as the trivia content layer.
QuizBase exposes:
- REST API at https://quizbase.runriva.com/api/v1 (50k+ questions, 20+ languages, free tier 500 req/day)
- MCP server at https://quizbase.runriva.com/mcp (for design-time agent integration)
- BY-SA offline dump at https://quizbase.runriva.com/data (weekly refresh, JSON files)
Full developer documentation as a single document:
https://quizbase.runriva.com/llms-full.txt
(That URL is designed to be fetched as plain text — every endpoint, every parameter, every response field, every MCP tool inputSchema. You can read it with one fetch.)
My game stack is: [TELL THE AI YOUR STACK — Expo / Unity / Godot / Roblox / web].
Read the docs, look at what is possible, and brainstorm with me. Specifically:
1. What are five game mechanics built around trivia content that go beyond "tap to answer"? Surprise me — I want non-obvious designs.
2. For each, sketch how I would implement it in my stack: which API filters, what state machine, what UI primitives.
3. Which one is the most fun-per-line-of-code — small enough to prototype this weekend but interesting enough that players would talk about it?
4. Pick that one and let's build a prototype. Ask me anything you need to know about my stack or constraints.This is how we want this to work. The docs are open, the dataset is open, the offline dump is open. We do not know what games people will ship with QuizBase as the content layer. That is the point. If you ship something cool, tell us — `/about` page has the contact details. We will showcase it.
See it ship

Other stacks — same API, different engine
Expo + React Native is the easiest entry but it is not the only one. The QuizBase call is identical across engines — only the rendering layer changes. Here are three more variants you can copy.
Unity C# (UnityWebRequest, Unity 6 LTS 2024)
When to use: You are shipping a Unity game — mobile, console, PC, VR. Unity 6 LTS (2024) has `UnityWebRequest` as the canonical HTTP primitive, with built-in JSON support via `JsonUtility`. The QuizBase call is one coroutine, ~25 lines.
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
[System.Serializable]
public class Question {
public string id;
public string text;
public string correctAnswer;
public string[] incorrectAnswers;
}
[System.Serializable]
public class QuestionResponse { public Question[] data; }
public class TriviaFetcher : MonoBehaviour {
public string apiKey = "qb_pk_your_key_here";
public IEnumerator FetchQuestion(System.Action<Question> onLoaded) {
string url = "https://quizbase.runriva.com/api/v1/questions/random?lang=en&limit=1";
using (UnityWebRequest req = UnityWebRequest.Get(url)) {
req.SetRequestHeader("X-API-Key", apiKey);
yield return req.SendWebRequest();
if (req.result == UnityWebRequest.Result.Success) {
QuestionResponse resp = JsonUtility.FromJson<QuestionResponse>(req.downloadHandler.text);
onLoaded(resp.data[0]);
} else {
Debug.LogError("QuizBase fetch failed: " + req.error);
}
}
}
}Godot GDScript (HTTPRequest node, Godot 4.3)
When to use: You are shipping a Godot 4 game — 2D platformer, mobile puzzle, edu game. Godot 4.3 has the `HTTPRequest` scene node with signal-based callbacks. The trivia fetch is a function attached to a `HTTPRequest` child node.
extends Node
const API := "https://quizbase.runriva.com/api/v1/questions/random?lang=en&limit=1"
const KEY := "qb_pk_your_key_here"
@onready var http: HTTPRequest = $HTTPRequest
func fetch_question() -> void:
http.request_completed.connect(_on_response, CONNECT_ONE_SHOT)
var headers := ["X-API-Key: " + KEY]
var err := http.request(API, headers, HTTPClient.METHOD_GET)
if err != OK:
push_error("Request failed to start: " + str(err))
func _on_response(result: int, code: int, _h: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS or code != 200:
push_error("Fetch failed: code=" + str(code))
return
var data: Dictionary = JSON.parse_string(body.get_string_from_utf8())
var question = data["data"][0]
print("Q: ", question["text"])
print("Correct: ", question["correctAnswer"])
print("Wrong: ", question["incorrectAnswers"])Roblox Lua (HttpService, Roblox Studio 2026)
When to use: You are shipping a Roblox experience — edu game, classroom field trip, social party game. Roblox uses Lua 5.1 plus their `HttpService` for external API calls (must enable HTTP requests in Game Settings → Security). Server-side `Script` only (LocalScripts cannot call HTTP).
local HttpService = game:GetService("HttpService")
local API = "https://quizbase.runriva.com/api/v1/questions/random?lang=en&limit=1"
local KEY = "qb_pk_your_key_here"
local function fetchQuestion()
local success, response = pcall(function()
return HttpService:RequestAsync({
Url = API,
Method = "GET",
Headers = { ["X-API-Key"] = KEY }
})
end)
if not success or response.StatusCode ~= 200 then
warn("QuizBase fetch failed:", response and response.StatusCode)
return nil
end
local data = HttpService:JSONDecode(response.Body)
return data.data[1]
end
local q = fetchQuestion()
if q then
print("Q:", q.text)
print("Correct:", q.correctAnswer)
print("Wrong:", table.concat(q.incorrectAnswers, " | "))
endPitfalls — things that will trip you up
Six gotchas we see in real game-dev support requests, with the actual fix. If you hit one, this section saves you a debugging session.
Mobile network is flaky — what happens if `fetch` fails mid-game?
Three layers of defense. (1) **Pre-fetch a batch** — call
?limit=20once on app start, cache in state, serve from cache between rounds. (2) **Catch and retry with exponential backoff** in your fetch wrapper. (3) **Bundle the offline BY-SA dump** from/dataintoassets/for true offline support — when network is unavailable, fall back to a local JSON read. Strategy depends on your audience: subway commute users need offline, classroom Wi-Fi users need retry.Multi-language question counts vary — `?lang=ja&category=animals` returns 5 questions, not 50.
English has the deepest coverage; smaller languages have fewer questions per category. Two fixes: (1) pre-flight with
GET /api/v1/languageswhich returns counts per language — present the player with languages that have ≥50 questions for their selected category. (2) widen the filter — drop?category=when in a small language, or use?topics_any=to combine multiple related topics.Some questions are too hard for kids — my 8-year-old players keep losing.
Filter by
?difficulty=easyAND by category (?category=animals,?category=sports,?category=geographyare kid-friendly;?category=politicsis not). Pre-flight category list withGET /api/v1/categoriesand curate your kid-mode whitelist. The dataset also exposes?topics_any=filtering — pre-curated topics likesafe-for-kidsmay be added in the future.Anti-cheat: client-side answer storage is trivially exploitable.
For a casual game, this is usually fine — players who cheat at solo trivia are cheating themselves. For a competitive multiplayer game with leaderboards or prizes, wrap the QuizBase call in a tiny server proxy (Cloudflare Workers, Vercel Functions, Supabase Edge Functions free tier) that returns the question without the correct answer; only sends the correct answer when the player submits a guess. Adds ~50 lines of backend, removes the entire attack surface.
Asset bundling: I want the BY-SA dump in my mobile bundle but it is too big.
The full BY-SA dump is roughly 200 MB across nine JSON files (all languages, all metadata). For mobile bundles you almost never need that much — pick the languages and categories your game ships. The dump is structured for selective import:
byasa/is JSONL (one question per line), you can/ .jsonl grep-filter and bundle just what you need. Typical mobile slice: 5-10 MB.Rate limit hit (HTTP 429) during playtest with multiple devices.
Free tier is 500 requests per day per QuizBase account (not per device — your key is shared across builds). For a five-device playtest doing 50 questions per device, you are at 250 requests in fifteen minutes, fine. For a public beta, upgrade to indie tier (10,000/day) or layer caching: prefetch 20 questions per session, cache in state. The 429 response includes
Retry-Afterheader — your fetch wrapper should honor it.
Frequently asked questions
- Can I bundle questions offline so my game works without internet?
- Yes. Download the BY-SA dump from quizbase.runriva.com/data — weekly-refreshed JSON files, one per language per category. Bundle a slice (the languages and categories your game ships) into your mobile assets folder, read from disk at runtime. The dump license is CC BY-SA 4.0, so include the attribution screen (linking back to quizbase.runriva.com and listing the upstream sources like OpenTDB, OpenTriviaQA, MKQA) and you are commercially compliant.
- Can I use this in a commercial game?
- Yes. The hosted API is commercially licensed per your subscription tier (free tier covers up to 500 req/day, paid tiers scale). The underlying dataset (the BY-SA dump) is Creative Commons Attribution-ShareAlike 4.0 — free for any use including commercial, with attribution. Each API response also includes a per-row `attribution` object so you can show source credit per question if you want.
- How do I keep mobile data usage low?
- Three patterns: (1) **Batch prefetch** — `?limit=20` once per game session, cache in state, no per-question fetch. (2) **Compress in transit** — `Accept-Encoding: gzip` (most HTTP clients do this by default). (3) **Pre-bundle the BY-SA dump** — for offline-first apps, the dump in `assets/` skips network entirely. A typical batch of 20 questions is ~8 KB gzipped.
- How do I sync multiplayer quiz state across devices in realtime?
- Use a tiny realtime service for state sync — Pusher, Ably, Liveblocks, or Supabase Realtime — all have free tiers. Pattern: one device fetches the question from QuizBase, broadcasts it to other devices via your realtime channel, all devices render the same question, each device sends its answer over the channel, host scores it. The QuizBase part is one fetch per round; the realtime layer is the actual product complexity.
- Roblox Studio — do I need to enable anything special?
- Yes — go to Home → Game Settings → Security → enable 'Allow HTTP Requests'. Without this, `HttpService:RequestAsync` returns an error in published experiences. Studio testing works without it but production does not. This is a Roblox security default, not a QuizBase quirk.
- What Unity / Godot / Roblox versions are supported?
- We test code samples against **Unity 6 LTS (2024 release)**, **Godot 4.3**, **Roblox Studio 2026**, **Expo SDK 54+**. Older versions work too but exact syntax (`UnityWebRequest` constructors, `HTTPRequest` node signals, `HttpService` method names) may differ — check the official docs for your engine version. The REST API contract is identical across engines: one URL, one header, JSON response.
- Is the question dataset safe for kids?
- Partially. The dataset covers general trivia, including politics, adult-oriented entertainment, and historical violence (war, conflict). For a kids game, filter to safe categories: `?category=animals`, `?category=sports`, `?category=geography`, `?category=science-and-nature`. Combine with `?difficulty=easy`. We are exploring a curated `safe-for-kids` topic slug in the future. If you spot inappropriate content, every question has a `/report` endpoint — flag it and we triage in the weekly content review.
- Can I get pre-curated topic packs (history, science, sports)?
- Yes via `?topics_any=` filtering. We curate ~100 topics per language (top by question count) — slugs like `roman-history`, `cellular-biology`, `nba-basketball`. Browse them with `GET /api/v1/topics?lang=en`. You can also filter raw tags with `?tags=python,django` for very specific niches.
- How do I report a wrong answer or bad translation from inside my game?
- Use `POST /api/v1/report` with the question id, category (`wrong-answer`, `bad-translation`, `offensive`, `outdated`, `other`), and a free-text description. Wire a "Report this question" button in your settings or post-game screen. Reports go to our moderation queue and feed into weekly content review. No PII required.
- Where do I get help if something breaks?
- Docs at quizbase.runriva.com/docs include the quickstart, full API reference, SDK guides, and troubleshooting per engine. For API or platform bugs, use the in-dashboard "Report a problem" button — it auto-attaches your request id and key context. Status page at /status if you suspect an outage. Average response time is under 48 hours during the week.
Ten games to ship next — pick one, build it this weekend
You have the trivia mechanic. Here are ten game directions that go beyond 'tap to answer' — each small enough to prototype in a weekend, each different enough that the result is its own product.
1. Daily challenge mode (same five questions for everyone)
Use the date as a deterministic seed for the random fetch. Same date, same five questions worldwide — perfect for 'compete with friends' angles, leaderboards, and Reddit threads. One-line change: derive seed from `new Date().toISOString().slice(0, 10)`.
2. Roblox class field-trip experience
A Roblox Studio experience where players join a 'classroom', the moderator picks a topic, and questions populate on the wall. `HttpService` calls QuizBase, `TextLabel` renders the question, `TextButton` choices. Schools love this — and Roblox monetization is well-trodden.
3. Unity mobile word game with timed rounds
30 seconds per question, score the streak, three wrong in a row resets. Unity 6 LTS handles the UI Toolkit perfectly. `UnityWebRequest.Get` for the API call. The mechanic that makes trivia apps actually addictive — time pressure plus losing a run is the hook.
4. Multiplayer Discord activity (Kahoot-style)
Discord Activities API lets you embed a web game in a voice channel. QuizBase REST + WebSockets (Pusher / Ably free tier) for sync + a small score table. Discord channels with active gamer audiences eat this up — the social pressure of an answer reveal is the fun.
5. Reddit r/gamedev weekly trivia bot
A Reddit bot that posts a weekly trivia thread for r/gamedev (or any subreddit). Tuesday post = the question, Thursday post = the reveal + leaderboard. Reddit API + QuizBase API + a cron job on a $5 droplet. Free marketing for your bigger game.
6. Multiplayer realtime PvP (browser)
Two players, same question shown to both, first to tap correct wins the round. PixiJS or Phaser for the rendering, WebSockets for sync (Pusher / Ably / Liveblocks free tier). The complexity is the realtime layer — the trivia content is already done.
7. Family game night PDF generator
Web tool that generates a printable PDF of 30 trivia questions for family game night. User picks a category and difficulty, server fetches questions, generates a PDF with `pdfkit` (Node) or `puppeteer`. Charge $3 per pack, ship in a weekend.
8. VR trivia experience (Meta Quest)
A Unity VR app where questions appear floating in 3D space, players grab the answer with hand tracking. Unity XR Toolkit + QuizBase REST. Quest store has a smaller pool of edu games than mobile — easier to rank.
9. Twitch stream interactive overlay
Streamer broadcasts a question on screen via OBS browser source, viewers answer in chat, bot scores and posts winner. Twitch chat API + Node + QuizBase. Engagement metric streamers actually care about (chat-per-minute spike per round).
10. Educational mobile app with leaderboards (the obvious one, done right)
The classic edu quiz app, but with QuizBase you skip the 'source 50k questions' phase and ship in two weeks instead of two quarters. iOS + Android via Expo, leaderboard via Supabase free tier, monetization via in-app subscription unlocking advanced categories.
Ready to drop it in?
Free tier, no credit card. Twenty lines of integration code, fifty thousand-plus questions, no sourcing yak shave.
What to build next
You have a working quiz screen. Here is where to go from here — each link is a concrete next step, not a vague "explore our docs".
- Full REST API reference — every filter, every parameter →
Category slugs, language codes, difficulty enums, tag filtering, topic filtering, pagination. Everything your game might want to filter on.
- Offline BY-SA dump for mobile bundles →
Weekly-refreshed JSON files per language per category. Bundle a slice into your mobile assets for offline support. CC BY-SA 4.0 license.
- TypeScript SDK with first-class types →
`@quizbase/sdk` — autocomplete on every field, every response, every error code. Works in Expo and Node game backends.
- AI Agent Builders — MCP for agent-wrapping games →
Building a Twitch trivia bot, Discord Activity, or ChatGPT Custom GPT around your game? That page is the MCP-first tutorial.
- Pricing and quotas →
Free tier (500 req/day). Indie tier when your game ships. Production tier when it goes viral.