> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quiz-quail.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Quiz Generation

> Generate quizzes with AI, discover trending topics, and get promotion suggestions

## 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

```bash theme={null}
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) |

### 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

| 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:

```javascript theme={null}
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.
  }
);
```

<Warning>
  The browser `EventSource` API only supports GET requests and cannot send
  custom headers. For API key authentication, use the `fetch`-based approach
  below.
</Warning>

### Consuming SSE with fetch (recommended)

This approach works in both browsers and Node.js, and supports POST requests
with API key authentication:

```javascript theme={null}
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`](https://www.npmjs.com/package/eventsource-parser) package:

```javascript theme={null}
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.

```bash theme={null}
curl "https://app.quiz-quail.com/api/v1/trends" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json theme={null}
{
  "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

```bash theme={null}
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:

```javascript theme={null}
// 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.

```bash theme={null}
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:

```javascript theme={null}
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.

```javascript theme={null}
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;
      }
    }
  }
}
```

<Info>
  **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.
</Info>
