Skip to main content

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

FieldTypeDefaultDescription
topicstringRequiredTopic for the quiz (max 500 chars)
options.roundsnumberNumber of rounds (1–10)
options.questionsPerRoundnumberQuestions per round (1–20)
options.difficultynumberDifficulty level (1–5)
options.questionTypesstring[]Preferred question types
options.questionDurationSecondsnumberSeconds to show each question (3–30)
options.answerDurationSecondsnumberSeconds to show each answer (2–15)
options.factDurationSecondsnumberSeconds to show each fact (2–15)
options.hintTimingPercentnumberWhen to show hint (0–100% of question)

SSE event format

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

TypeFieldsDescription
progressmessageHuman-readable status update
donequizIdQuiz created successfully
errormessageGeneration 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.
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 }));
      }
    })();
  });
}
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:
CategoryDescription
pop_cultureMovies, music, celebrities
sportsSports events and athletes
science_techScience and technology
entertainmentTV, streaming, gaming culture
world_eventsNews and current affairs
gamingVideo games and esports
food_lifestyleFood, 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}`);

Promotion suggestions

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.