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

# Quickstart

> Go from zero to your first quiz video in under 5 minutes.

Your first API call is moments away. This guide walks you through creating a quiz, adding questions, rendering it to video, and downloading the result — all from the command line or your own code.

## Prerequisites

* A Quiz Quail account — [sign up free](https://app.quiz-quail.com)
* An API key — generate one in [Settings > API Keys](https://app.quiz-quail.com/settings/api-keys)
* `curl` (or any HTTP client / language of your choice)

<Note>
  Your API key is a **secret**. Never expose it in client-side code, public repos, or browser requests. Always send it server-side via the `Authorization` header.
</Note>

## Base URL

All API requests are made to:

```
https://app.quiz-quail.com/api/v1
```

Authenticate every request with the `Authorization: Bearer` header:

```
Authorization: Bearer YOUR_API_KEY
```

***

<Steps>
  ### Verify your key

  Make sure everything is wired up by listing your quizzes.

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

    ```typescript fetch theme={null}
    const res = await fetch("https://app.quiz-quail.com/api/v1/quizzes", {
      headers: { Authorization: "Bearer YOUR_API_KEY" },
    });
    const { data, pagination } = await res.json();
    console.log(`You have ${pagination.total} quizzes`);
    ```
  </CodeGroup>

  You should see a response like this:

  ```json theme={null}
  {
    "data": [],
    "pagination": {
      "cursor": null,
      "hasMore": false,
      "total": 0
    }
  }
  ```

  If you get a `401`, double-check your API key. If you get a `403`, make sure your key has the `quizzes:read` scope.

  ### Create a quiz

  Let's create your first quiz. The only required field is `title` (and even that defaults to "Untitled Quiz" if omitted).

  <CodeGroup>
    ```bash curl theme={null}
    curl -X POST https://app.quiz-quail.com/api/v1/quizzes \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"title": "My First API Quiz"}'
    ```

    ```typescript fetch theme={null}
    const res = await fetch("https://app.quiz-quail.com/api/v1/quizzes", {
      method: "POST",
      headers: {
        Authorization: "Bearer YOUR_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ title: "My First API Quiz" }),
    });
    const { data: quiz } = await res.json();
    console.log("Quiz created:", quiz.id);
    ```
  </CodeGroup>

  The response includes the full quiz with a default "Round 1" already created:

  ```json theme={null}
  {
    "data": {
      "id": "abc123-...",
      "title": "My First API Quiz",
      "status": "draft",
      "rounds": [
        {
          "id": "round-456-...",
          "title": "Round 1",
          "order": 0,
          "questions": []
        }
      ],
      "themes": null,
      "created_at": "2026-03-15T...",
      "updated_at": "2026-03-15T..."
    }
  }
  ```

  <Tip>
    Save the `quiz.id` and `rounds[0].id` — you'll need them in the next steps.
  </Tip>

  ### Add questions

  Now let's add a multiple-choice question to Round 1. Replace `QUIZ_ID` and `ROUND_ID` with the IDs from the previous response.

  <CodeGroup>
    ```bash curl theme={null}
    curl -X POST https://app.quiz-quail.com/api/v1/quizzes/QUIZ_ID/rounds/ROUND_ID/questions \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "question_type": "multiple_choice",
        "content": {
          "text": "What is the largest planet in our solar system?",
          "options": ["Mars", "Jupiter", "Saturn", "Neptune"],
          "correctOptionIndex": 1
        },
        "hint": "It has a famous Great Red Spot.",
        "fact": "Jupiter is so large that over 1,300 Earths could fit inside it."
      }'
    ```

    ```typescript fetch theme={null}
    const res = await fetch(
      `https://app.quiz-quail.com/api/v1/quizzes/${quizId}/rounds/${roundId}/questions`,
      {
        method: "POST",
        headers: {
          Authorization: "Bearer YOUR_API_KEY",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          question_type: "multiple_choice",
          content: {
            text: "What is the largest planet in our solar system?",
            options: ["Mars", "Jupiter", "Saturn", "Neptune"],
            correctOptionIndex: 1,
          },
          hint: "It has a famous Great Red Spot.",
          fact: "Jupiter is so large that over 1,300 Earths could fit inside it.",
        }),
      }
    );
    const { data: question } = await res.json();
    console.log("Question created:", question.id);
    ```
  </CodeGroup>

  ```json theme={null}
  {
    "data": {
      "id": "q-789-...",
      "question_type": "multiple_choice",
      "content": {
        "text": "What is the largest planet in our solar system?",
        "options": ["Mars", "Jupiter", "Saturn", "Neptune"],
        "correctOptionIndex": 1
      },
      "hint": "It has a famous Great Red Spot.",
      "fact": "Jupiter is so large that over 1,300 Earths could fit inside it.",
      "countdown_seconds": 10,
      "difficulty": null,
      "order": 0
    }
  }
  ```

  Quiz Quail supports 13 question types. Here are a few to try:

  | Type              | Description                             |
  | ----------------- | --------------------------------------- |
  | `multiple_choice` | Classic A/B/C/D with one correct answer |
  | `true_false`      | True or false statement                 |
  | `image`           | Image-based multiple choice             |
  | `ranking`         | Put items in the correct order          |
  | `open_reveal`     | Open-ended with a reveal answer         |
  | `emoji`           | Guess from emoji clues                  |

  See [Quizzes Guide](/guides/quizzes) for the full list with content schemas.

  ### Render to video

  Once your quiz has at least one question, you can render it to video. This kicks off an asynchronous job — the API responds immediately with a `202 Accepted` and a job ID you can poll.

  <CodeGroup>
    ```bash curl theme={null}
    curl -X POST https://app.quiz-quail.com/api/v1/renders \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"quiz_id": "QUIZ_ID"}'
    ```

    ```typescript fetch theme={null}
    const res = await fetch("https://app.quiz-quail.com/api/v1/renders", {
      method: "POST",
      headers: {
        Authorization: "Bearer YOUR_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ quiz_id: quizId }),
    });
    // 202 Accepted
    const { data: job } = await res.json();
    console.log("Render started:", job.id, "Status:", job.status);
    ```
  </CodeGroup>

  ```json theme={null}
  {
    "data": {
      "id": "render-abc-...",
      "quiz_id": "abc123-...",
      "status": "pending",
      "output_url": null,
      "error_message": null,
      "created_at": "2026-03-15T...",
      "completed_at": null
    }
  }
  ```

  <Note>
    The render endpoint returns `202 Accepted` (not `200 OK`), along with a `Location` header pointing to the job's status URL. Only one render can be in progress per quiz at a time — attempting a second returns a `409 Conflict`.
  </Note>

  ### Poll for completion

  The render job moves through these statuses: `pending` -> `claimed` -> `rendering` -> `done` (or `failed`). Poll the status endpoint until it completes.

  <CodeGroup>
    ```bash curl theme={null}
    # Poll every 5 seconds
    while true; do
      STATUS=$(curl -s https://app.quiz-quail.com/api/v1/renders/RENDER_ID \
        -H "Authorization: Bearer YOUR_API_KEY" | jq -r '.data.status')
      echo "Status: $STATUS"
      if [ "$STATUS" = "done" ] || [ "$STATUS" = "failed" ]; then break; fi
      sleep 5
    done
    ```

    ```typescript fetch theme={null}
    async function pollRender(jobId: string): Promise<string> {
      while (true) {
        const res = await fetch(
          `https://app.quiz-quail.com/api/v1/renders/${jobId}`,
          { headers: { Authorization: "Bearer YOUR_API_KEY" } }
        );
        const { data: job } = await res.json();
        console.log("Status:", job.status);

        if (job.status === "done" || job.status === "failed") {
          return job.status;
        }
        // Wait 5 seconds before polling again
        await new Promise((r) => setTimeout(r, 5000));
      }
    }

    await pollRender(job.id);
    ```
  </CodeGroup>

  <Tip>
    Instead of polling, you can set up a [webhook](/webhooks) to receive a `render.completed` or `render.failed` event when the job finishes.
  </Tip>

  ### Download the video

  Once the render status is `done`, grab a signed download URL. The URL is valid for 1 hour.

  <CodeGroup>
    ```bash curl theme={null}
    curl https://app.quiz-quail.com/api/v1/renders/RENDER_ID/download \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```

    ```typescript fetch theme={null}
    const res = await fetch(
      `https://app.quiz-quail.com/api/v1/renders/${jobId}/download`,
      { headers: { Authorization: "Bearer YOUR_API_KEY" } }
    );
    const { data } = await res.json();
    console.log("Download URL:", data.url);
    console.log("Expires in:", data.expires_in, "seconds");
    ```
  </CodeGroup>

  ```json theme={null}
  {
    "data": {
      "url": "https://storage.quizquail.com/renders/render-abc-....mp4?token=...",
      "expires_in": 3600
    }
  }
  ```
</Steps>

***

## Full script

Here's the entire flow as a single TypeScript script you can copy and run:

```typescript full-example.ts theme={null}
const API_KEY = process.env.QUIZ_QUAIL_API_KEY!;
const BASE = "https://app.quiz-quail.com/api/v1";

const headers = {
  Authorization: `Bearer ${API_KEY}`,
  "Content-Type": "application/json",
};

// 1. Create a quiz
const quizRes = await fetch(`${BASE}/quizzes`, {
  method: "POST",
  headers,
  body: JSON.stringify({ title: "Solar System Trivia" }),
});
const { data: quiz } = await quizRes.json();
const roundId = quiz.rounds[0].id;
console.log("Quiz created:", quiz.id);

// 2. Add questions
const questions = [
  {
    question_type: "multiple_choice",
    content: {
      text: "What is the largest planet in our solar system?",
      options: ["Mars", "Jupiter", "Saturn", "Neptune"],
      correctOptionIndex: 1,
    },
    hint: "It has a famous Great Red Spot.",
  },
  {
    question_type: "true_false",
    content: {
      text: "Venus is the hottest planet in our solar system.",
      correctAnswer: true,
    },
    fact: "Venus has a thick atmosphere that traps heat, making it hotter than Mercury.",
  },
  {
    question_type: "multiple_choice",
    content: {
      text: "How many moons does Mars have?",
      options: ["0", "1", "2", "4"],
      correctOptionIndex: 2,
    },
  },
];

for (const q of questions) {
  await fetch(`${BASE}/quizzes/${quiz.id}/rounds/${roundId}/questions`, {
    method: "POST",
    headers,
    body: JSON.stringify(q),
  });
}
console.log(`Added ${questions.length} questions`);

// 3. Start a render
const renderRes = await fetch(`${BASE}/renders`, {
  method: "POST",
  headers,
  body: JSON.stringify({ quiz_id: quiz.id }),
});
const { data: job } = await renderRes.json();
console.log("Render started:", job.id);

// 4. Poll until done
let status = job.status;
while (status !== "done" && status !== "failed") {
  await new Promise((r) => setTimeout(r, 5000));
  const pollRes = await fetch(`${BASE}/renders/${job.id}`, { headers });
  const { data } = await pollRes.json();
  status = data.status;
  console.log("Status:", status);
}

// 5. Download
if (status === "done") {
  const dlRes = await fetch(`${BASE}/renders/${job.id}/download`, { headers });
  const { data } = await dlRes.json();
  console.log("Download your video:", data.url);
}
```

## What's next?

You just created a quiz, added questions, and rendered a video — all through the API. Here's where to go from here:

* **[Authentication](/authentication)** — Scopes, key rotation, and security best practices
* **[Quizzes Guide](/guides/quizzes)** — All 13 question types, rounds, and reordering
* **[Themes Guide](/guides/themes)** — Customize colors, fonts, and layouts
* **[Rendering Guide](/guides/rendering)** — Resolution options, webhooks, and bulk rendering
* **[AI Generation Guide](/guides/ai-generation)** — Generate entire quizzes from a text prompt
* **[API Reference](/api-reference/overview)** — Complete endpoint documentation
