Overview
Quiz Quail’s AI features let you:
- Generate complete quizzes from a text prompt via streaming SSE
- Discover trending topics to create timely, engaging quizzes
- Get promotion suggestions for maximizing reach on social platforms
Generate a quiz
Quiz generation uses Server-Sent Events (SSE) to stream progress updates
in real time. The AI agent researches your topic, creates questions with media,
and saves the quiz to your account.
Request
curl -X POST https://app.quiz-quail.com/api/v1/quizzes/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"topic": "The Solar System",
"options": {
"rounds": 2,
"questionsPerRound": 5,
"difficulty": 3
}
}'
Options
| Field | Type | Default | Description |
|---|
topic | string | Required | Topic for the quiz (max 500 chars) |
options.rounds | number | — | Number of rounds (1–10) |
options.questionsPerRound | number | — | Questions per round (1–20) |
options.difficulty | number | — | Difficulty level (1–5) |
options.questionTypes | string[] | — | Preferred question types |
options.questionDurationSeconds | number | — | Seconds to show each question (3–30) |
options.answerDurationSeconds | number | — | Seconds to show each answer (2–15) |
options.factDurationSeconds | number | — | Seconds to show each fact (2–15) |
options.hintTimingPercent | number | — | When to show hint (0–100% of question) |
The response is a text/event-stream. Each event is a JSON object on a data: line:
data: {"type":"progress","message":"Preparing your quiz..."}
data: {"type":"progress","message":"Researching your topic..."}
data: {"type":"progress","message":"Finding stock images..."}
data: {"type":"done","quizId":"quiz_abc123"}
Event types
| Type | Fields | Description |
|---|
progress | message | Human-readable status update |
done | quizId | Quiz created successfully |
error | message | Generation failed |
Consuming SSE with EventSource
The simplest way to consume the stream in a browser:
const API_KEY = "your_api_key";
const eventSource = new EventSource(
"https://app.quiz-quail.com/api/v1/quizzes/generate", {
// Note: EventSource doesn't support POST or custom headers natively.
// Use the fetch-based approach below for API key auth.
}
);
The browser EventSource API only supports GET requests and cannot send
custom headers. For API key authentication, use the fetch-based approach
below.
Consuming SSE with fetch (recommended)
This approach works in both browsers and Node.js, and supports POST requests
with API key authentication:
const API_KEY = "your_api_key";
const BASE = "https://app.quiz-quail.com/api/v1";
async function generateQuiz(topic, options = {}) {
const response = await fetch(`${BASE}/quizzes/generate`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ topic, options }),
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Parse complete SSE events from the buffer
const lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete line in buffer
for (const line of lines) {
if (line.startsWith("data: ")) {
const event = JSON.parse(line.slice(6));
switch (event.type) {
case "progress":
console.log(`Progress: ${event.message}`);
break;
case "done":
console.log(`Quiz created: ${event.quizId}`);
return event.quizId;
case "error":
throw new Error(`Generation failed: ${event.message}`);
}
}
}
}
}
// Usage
const quizId = await generateQuiz("The Solar System", {
rounds: 2,
questionsPerRound: 5,
difficulty: 3,
});
Node.js example
In Node.js 18+, the same fetch-based approach works natively. For older
Node.js versions or if you prefer an event-driven API, use the
eventsource-parser package:
import { createParser } from "eventsource-parser";
async function generateQuiz(topic) {
const response = await fetch(
"https://app.quiz-quail.com/api/v1/quizzes/generate",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.QUIZQUAIL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ topic }),
}
);
return new Promise((resolve, reject) => {
const parser = createParser({
onEvent(event) {
const data = JSON.parse(event.data);
if (data.type === "done") resolve(data.quizId);
if (data.type === "error") reject(new Error(data.message));
if (data.type === "progress") console.log(data.message);
},
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
(async () => {
while (true) {
const { done, value } = await reader.read();
if (done) break;
parser.feed(decoder.decode(value, { stream: true }));
}
})();
});
}
Trending topics
Get currently trending topics to create timely quizzes. Trends are refreshed
automatically and cached for performance.
curl "https://app.quiz-quail.com/api/v1/trends" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"title": "Mars Rover Discovers New Mineral Formation",
"hook": "NASA's latest discovery has everyone talking",
"quizPrompt": "Recent Mars exploration discoveries and space science",
"category": "science_tech",
"timescale": "this_week"
}
],
"refreshedAt": "2026-03-15T08:00:00Z"
}
Filter by category
curl "https://app.quiz-quail.com/api/v1/trends?category=sports" \
-H "Authorization: Bearer YOUR_API_KEY"
Available categories:
| Category | Description |
|---|
pop_culture | Movies, music, celebrities |
sports | Sports events and athletes |
science_tech | Science and technology |
entertainment | TV, streaming, gaming culture |
world_events | News and current affairs |
gaming | Video games and esports |
food_lifestyle | Food, travel, lifestyle |
Using a trend to generate a quiz
Combine trending topics with quiz generation for timely content:
// 1. Fetch trending topics
const trendsRes = await fetch(`${BASE}/trends`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const { data: trends } = await trendsRes.json();
// 2. Pick a trend and generate a quiz from its prompt
const trend = trends[0];
const quizId = await generateQuiz(trend.quizPrompt, {
rounds: 3,
questionsPerRound: 5,
difficulty: 3,
});
console.log(`Created quiz from trend "${trend.title}": ${quizId}`);
After creating a quiz, get AI-powered promotion suggestions including optimized
social media copy, hashtags, and posting strategy. This endpoint also streams
via SSE.
curl -X POST https://app.quiz-quail.com/api/v1/promote \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "quizId": "QUIZ_ID" }'
The response streams SSE events:
data: {"type":"progress","message":"Researching promotion channels & SEO trends..."}
data: {"type":"progress","message":"Building promotion strategy..."}
data: {"type":"done","data":"{\"hashtags\":[...],\"platforms\":[...]}"}
The done event’s data field is a JSON string containing the full promotion
strategy. Parse it to get structured suggestions:
async function getPromotionSuggestions(quizId) {
const response = await fetch(`${BASE}/promote`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ quizId }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith("data: ")) {
const event = JSON.parse(line.slice(6));
if (event.type === "done") {
return JSON.parse(event.data);
}
if (event.type === "error") {
throw new Error(event.message);
}
}
}
}
}
Results are cached per quiz. Pass "force": true to regenerate.
Full example: trend to published video
This end-to-end example finds a trending topic, generates a quiz, renders it,
and uploads to YouTube.
const API_KEY = "your_api_key";
const BASE = "https://app.quiz-quail.com/api/v1";
const headers = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
async function trendToYouTube(channelId) {
// 1. Get trending topics
const trendsRes = await fetch(`${BASE}/trends`, { headers });
const { data: trends } = await trendsRes.json();
const trend = trends[0];
console.log(`Using trend: "${trend.title}"`);
// 2. Generate quiz (SSE)
const genRes = await fetch(`${BASE}/quizzes/generate`, {
method: "POST",
headers,
body: JSON.stringify({
topic: trend.quizPrompt,
options: { rounds: 3, questionsPerRound: 5 },
}),
});
const quizId = await consumeSSE(genRes, (event) => {
if (event.type === "progress") console.log(event.message);
if (event.type === "done") return event.quizId;
if (event.type === "error") throw new Error(event.message);
});
// 3. Render video
const renderRes = await fetch(`${BASE}/renders`, {
method: "POST",
headers,
body: JSON.stringify({ quiz_id: quizId }),
});
const { data: renderJob } = await renderRes.json();
await pollUntilDone(`${BASE}/renders/${renderJob.id}`, headers);
// 4. Generate YouTube metadata
const metaRes = await fetch(`${BASE}/youtube/generate-metadata`, {
method: "POST",
headers,
body: JSON.stringify({ quizId }),
});
const { data: metadata } = await metaRes.json();
// 5. Upload to YouTube
const uploadRes = await fetch(`${BASE}/youtube/uploads`, {
method: "POST",
headers,
body: JSON.stringify({
quizId,
renderJobId: renderJob.id,
channelId,
metadata: {
title: metadata.title,
description: metadata.description,
tags: metadata.tags,
visibility: "unlisted",
},
}),
});
const { data: uploadJob } = await uploadRes.json();
const upload = await pollUntilDone(
`${BASE}/youtube/uploads/${uploadJob.id}`,
headers
);
console.log(`Published: ${upload.youtube_video_url}`);
return upload;
}
// Helper: poll a job endpoint until status is "done" or "failed"
async function pollUntilDone(url, headers) {
while (true) {
await new Promise((r) => setTimeout(r, 5000));
const res = await fetch(url, { headers });
const { data } = await res.json();
if (data.status === "done") return data;
if (data.status === "failed") throw new Error(data.error_message);
}
}
// Helper: consume an SSE response, calling handler for each event
async function consumeSSE(response, handler) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith("data: ")) {
const event = JSON.parse(line.slice(6));
const result = handler(event);
if (result !== undefined) return result;
}
}
}
}
Required scopes: ai:write + ai:read for quiz generation, trends,
and promotion suggestions. Add renders:write, renders:read, youtube:write,
and youtube:read for the full pipeline.