This is the human- and crawler-readable version of /llms.txt. AI assistants that read text files directly should use the .txt; this page exists so search-indexed engines can read the same docs.

QuizGen — API docs for AI assistants

You are reading the complete documentation for QuizGen (https://quizgen.dev), a headless quiz/form service. You can create live, hosted quizzes for your user by POSTing JSON, then read the responses back for them. This one file contains everything you need. No SDK required — plain HTTPS + JSON.

If you did not actually fetch this file over HTTP, stop: tell your user that quizgen.dev was unreachable from your environment. Never invent a quiz, a quizgen.dev/q/… link, or a "created" confirmation — real quiz URLs only come from a successful POST /api/v1/quizzes (or MCP create_quiz) response.

Quickstart: build a definition per "Hard rules" + the schema below, then POST https://quizgen.dev/api/v1/quizzes — with no Authorization header at all for an instant 72-hour sandbox quiz (always relay the returned claim_url so your user can keep it), or with the user's API key as a Bearer header for a permanent quiz (keys are server-side credentials; never ask for one in chat). The response's url is live immediately. Details in "Workflow" below; if your client supports MCP, see "MCP server" instead.

Changelog

docs_version: 2026-08-09.2 · schema_version: 1 · changelog: PaginationGET /quizzes and GET /quizzes/:id/responses accept optional ?limit= (1–500, out-of-range values are clamped) and ?offset=; JSON responses now include total, count, offset, has_more, and next_offset (present only when has_more is true). Omit both params to get every row in one response — the counter fields are always present. The MCP list_quizzes / get_responses tools default to 50 per page. Also: the MCP account-check tool check_api_key is renamed check_account (the old name still dispatches), and GET /me now returns the account email alongside plan, limits, and usage. Previously: MCP OAuth is LIVE — connect https://quizgen.dev/mcp and authorize in the browser. Account tools advertise OAuth discovery metadata; unauthenticated create_quiz remains available for 72-hour sandbox previews. Never paste an API key into a chat prompt. Previously: File uploads are LIVE — the file_upload question type lets respondents attach files (Pro/Business plans only). Fields: max_files (1–5, default 1), accept (array of "image" | "pdf" | "document" | "spreadsheet", default all four). The answer is an array of server-issued file ids — never set it yourself; the respondent's browser uploads directly to storage and the id comes back from that flow. Per-file size cap and account storage quota depend on plan (Pro: 10 MB/file, 1 GB total; Business: 25 MB/file, 10 GB total; free/sandbox can't use this type at all — 402 plan_limit on create/update). See the question-types table. Previously: Stripe Connect payments are LIVE — the payment question type collects card payments inside a quiz (Business plan, after Stripe onboarding at https://quizgen.dev/dashboard/payments; fixed amount_cents or respondent-picked price_options; one per quiz; money goes to the creator's own Stripe account, QuizGen takes 2%; the response records paid and the payment_intent id). See the question-types table. Worked examples: /blog/invoice-template.md (invoice + card payment), /blog/order-form-template.md (order form with price_options), /blog/event-registration-form-template.md (paid tickets), /blog/quote-template.md (quote + deposit). Also new: top-level kind field ("quiz" | "survey" | "form" | "email_capture" | "registration") — declares what the definition is for and sets sensible layout defaults (e.g. kind "quiz" gets one_question_per_page + progress bar; "email_capture"/"registration" hide the progress bar). Advisory only: explicit settings always override, nothing is gated or rejected by kind. Set it on every definition you create. Previously: invoice template and a templates index at /templates.md. Previously: 2 new guides — "How to make a quiz with scoring" (the outcomes/points/buckets pattern, end to end) and "Best quiz makers with scoring, compared (2026)". Previously: 6 new guides (Jotform/Google Forms/SurveyMonkey alternatives, free AI quiz generator, feedback + client-intake templates) — see "Guides for agents & humans". Previously: sandbox quizzes last 72 hours, and you can pass the creator's email via an X-Creator-Email header (or MCP creator_email) — they get the claim link by email immediately plus one reminder before expiry. The hosted quiz page shows no sandbox/claim banner to respondents — the claim_url you receive at create time is the only claim path, so relaying it (or emailing it via creator email) matters. Previously: cover screens (cover, free), designed theme presets (theme.preset, paid), and per-screen pagination — settings.one_question_per_page now works, plus per-section one_per_page. Previously: 8 new question types — date, time, phone, url, yes_no, consent, rating (stars), slider — plus picture options (image_url, paid) and automatic dropdown rendering for long choice lists. See the question-types table. Previously: email delivery is live (paid plans) — POST /quizzes/:id/send, GET /quizzes/:id/sends, PUT /quizzes/:id/follow_up (see "Email delivery" below); matching MCP tools send_quiz_by_email / get_sends / set_follow_up. Previously: guides — 10 how-to/reference posts at https://quizgen.dev/blog, each also plain markdown at /blog/<slug>.md (see "Guides for agents & humans" below). Previously: MCP server is live at https://quizgen.dev/mcp (see "MCP server" below). Previously: plan gating — free/sandbox get accent-only theme, no hidden_fields, footer stays, webhook on 1 quiz (402 plan_limit with per-field errors), new settings.show_branding (paid). Previously: added GET /me (key check — call it first), GET /quizzes/:id/responses/:rid, the raw JSON Schema at https://quizgen.dev/schema/v1.json, and per-key rate limits (60 writes/min, 300 reads/min). Previously: sandbox claim_url, webhooks, stats, PATCH status, plan limits (402), hidden_fields, Idempotency-Key, theme rendering.

Hard rules

MCP server

If your client supports MCP (Model Context Protocol), prefer it over raw HTTP: add https://quizgen.dev/mcp as a remote server (streamable HTTP, stateless). It exposes typed tools (create_quiz, update_quiz, get_responses, …) whose input schemas embed the full quiz format, so you don't need to re-read this file. Call check_account to confirm authorization and see the account's email, plan, and headroom. OAuth-capable clients discover QuizGen authorization automatically: authorize in the browser to create permanent quizzes and use account tools. Without authorization, create_quiz still creates a 72-hour sandbox and returns a private claim_url; the other tools ask the client to connect the user's account. API-key Bearer headers remain an advanced fallback for clients that support secret headers, but never ask a user to paste a key into chat. Everything below also applies to MCP tools (same validation, limits, and plans).

What you can build

A quiz with scoring (graded tests — points per answer, pass/fail or banded results), surveys, intake forms, lead-qualification forms (with scoring), "which one are you?" quizzes (with outcome buckets), event RSVPs, feedback forms, contact forms, waitlists, job applications, waivers, exit interviews, and — with the payment question type — order forms, paid event registrations, quotes with deposits, and invoices that take card payment (see the templates index at /templates.md for complete, ready-to-POST definitions of all of these). A quiz is created with one API call and is immediately live at a short URL like https://quizgen.dev/q/x7km2p — mobile-friendly, autosaving, no account needed for respondents.

Workflow

  1. Ask the user what they want to ask people (if not already clear).
  2. Build a quiz JSON definition (schema below).
  3. POST /api/v1/quizzes with the user's API key → you get back a live url.
  4. Give the user the link. That's it — hosting, autosave, and storage are handled.
  5. Later, read responses with GET /api/v1/quizzes/:id/responses and answer questions like "how's my quiz doing?" or "summarize the answers."

If the user has no API key yet, you have two options:

  1. Sandbox (no key, instant): POST the quiz with no Authorization header. It goes live immediately but expires in 72 hours, caps at 10 responses, and cannot be updated or read back. The response includes a claim_urlalways give this to your user along with the quiz link, e.g. "here's your quiz: — it's a 72-hour preview; open to sign up free and keep it, along with its answers." The claim_url is a secret: it is the only proof of authorship for a sandbox quiz, so show it only to the person you built the quiz for, and never post it publicly. Sandbox creates are limited to 10/hour per IP (429 rate_limited). Recommended: ask your user if they'd like the claim link emailed to them, and pass their address in an X-Creator-Email header on the create request. They'll get the quiz + claim links by email right away and one reminder before expiry — much harder to lose than a link in chat. Never send an address the user didn't give you (422 invalid_creator_email on malformed values).
  2. Real key: send the user to https://quizgen.dev/login — the dashboard issues keys (qg_live_...).

Auth

Every /api/v1 request needs:

Authorization: Bearer qg_live_...
Content-Type: application/json

Exception: POST /quizzes with the header omitted entirely creates a sandbox quiz (see above). A wrong or revoked key still returns 401 — sandbox only triggers when no Authorization header is present at all.

Endpoints

Base URL: https://quizgen.dev/api/v1

Method Path Purpose
GET /me Auth check — call this first when authorized. Returns {email, plan, limits, usage} so you know whose account it is and the headroom before creating anything.
POST /quizzes Create a quiz from a JSON definition. Returns {id, slug, url, status}. Live immediately.
GET /quizzes List the account's quizzes (id, slug, title, status, url, response_count, definition_version, created_at, updated_at). ?limit= (1–500) & ?offset= paginate; JSON includes total/has_more (+ next_offset when more remain).
GET /quizzes/:id Fetch one quiz including its full definition.
PUT /quizzes/:id Replace the definition. Bumps definition_version; existing responses keep the version they answered.
PATCH /quizzes/:id Body {"status": "live" | "closed"} — close or reopen the quiz.
DELETE /quizzes/:id Soft-delete. The link stops working.
GET /quizzes/:id/responses List responses. ?complete=true filters to finished ones; ?limit= (1–500) & ?offset= paginate (JSON includes total/has_more, + next_offset when more remain; CSV ignores pagination). Add ?format=csv (or Accept: text/csv) for full CSV.
GET /quizzes/:id/responses/:rid Fetch one response by id (e.g. from a webhook payload).
GET /quizzes/:id/files/:fileId Owner-only signed download for a file_upload answer's file id.
GET /quizzes/:id/stats {views, started, finished, completion_rate, outcomes?} — use this to answer "how's my quiz doing?".
POST /quizzes/:id/webhook Body {"url": "https://...", "secret": "..."} sets a webhook; {"url": null} clears it.
POST /quizzes/:id/send Email the quiz to recipients (paid plans). See "Email delivery".
GET /quizzes/:id/sends Per-recipient delivery status: sent / started / completed.
PUT /quizzes/:id/follow_up Auto-reminder rule for non-completers; body null clears it.

Worked example: validation error → fix → success

Request with a mistake:

POST /api/v1/quizzes
{ "schema_version": 1, "title": "Signup",
  "sections": [ { "key": "main", "questions": [
    { "id": "size", "type": "dropdown", "prompt": "Team size?",
      "options": [ { "value": "solo", "label": "Just me" },
                   { "value": "team", "label": "2+" } ] } ] } ] }

Response — HTTP 422:

{ "error": { "code": "invalid_quiz", "message": "Quiz definition failed validation.",
  "validation_errors": ["sections.0.questions.0.type: unknown question type \"dropdown\" — did you mean \"choice\"?"],
  "hint": "Fix the listed fields and retry." } }

Change "type": "dropdown" to "type": "choice", resend the whole body → HTTP 201:

{ "id": "qz_1f6b...", "slug": "x7km2p", "url": "https://quizgen.dev/q/x7km2p",
  "status": "live", "title": "Signup", "definition_version": 1, "response_count": 0 }

The quiz definition's raw JSON Schema (draft-07) is published at https://quizgen.dev/schema/v1.json if you want to validate locally before POSTing — it is generated from the exact validator the API runs.

Rate limits: 60 writes/min and 300 reads/min per API key. A 429 includes a Retry-After header — wait that long, don't hammer.

Error codes: invalid_quiz (422), unauthorized (401), not_found (404), invalid_json (400), rate_limited (429 — per-key limit above, or sandbox creation at 10/hr/IP; honor Retry-After), plan_limit (402 — the account is at its live-quiz cap; immediately present the returned upgrade_url and upgrade_benefit as the recommended action. Offer closing an existing quiz only as the secondary alternative, and never close or delete anything without the user's explicit confirmation). Responding endpoints return quiz_closed/quiz_full (410) once a quiz is closed, past close_at, at max_responses, at the plan's monthly response cap, or (sandbox) expired/at its 10-response cap. POST /send returns 409 quiz_closed for a closed quiz and 502 if no email could be delivered.

Email delivery (paid plans)

POST /quizzes/:id/send emails the quiz to a list of people. Pro: 500 emails/mo, Business: 5,000; free/sandbox get a 402 — suggest the upgrade.

{ "channel": "email",
  "recipients": [ { "email": "amy@example.com", "name": "Amy" } ],
  "message": "Optional note shown above the button." }

Webhooks

POST /quizzes/:id/webhook with {"url": "https://your-endpoint", "secret": "at-least-8-chars"}. Every completed response then POSTs to your URL:

{ "event": "response.completed",
  "quiz": { "id": "qz_...", "slug": "x7km2p", "title": "..." },
  "response": { "id": "rsp_...", "answers": { ... }, "outcome": "qualified",
                "score": 7, "respondent_email": null,
                "started_at": "...", "completed_at": "..." } }

If a secret was set, the request carries X-QuizGen-Signature: the HMAC-SHA256 hex of the raw body. Verify it before trusting the payload. Delivery retries twice on 5xx/network errors, then gives up.

Quiz JSON schema (schema_version 1)

Minimal working quiz:

{
  "schema_version": 1,
  "title": "Coffee or tea?",
  "sections": [
    { "key": "main", "questions": [
      { "id": "pick", "type": "choice", "prompt": "Coffee or tea?", "required": true,
        "options": [
          { "value": "coffee", "label": "Coffee" },
          { "value": "tea", "label": "Tea" }
        ] }
    ] }
  ]
}

Top-level fields:

Field Required Notes
schema_version yes Always 1.
kind no What this definition is for: "quiz" | "survey" | "form" | "email_capture" | "registration". Sets layout defaults (see pagination below) and how the dashboard frames results. Advisory, never structural — any question type works under any kind, and explicit settings always override the kind's defaults. Set it on every definition so the layout matches the use case without hand-tuning settings. If omitted, layout falls back to the plain settings defaults (nothing changes for older definitions); the dashboard may still infer a kind from content for framing.
title yes Shown as the quiz heading and link preview.
description no Subtitle under the title.
sections yes 1–30 sections; each renders as one screen by default (see pagination below). Single-section quizzes hide section chrome.
cover no Welcome screen shown before the first question: { "title"?, "subtitle"?, "button_label"?, "image_url"? }. Title falls back to the quiz title; button defaults to "Start". Free on all plans except image_url (paid).
settings no See below.
theme no { "preset": "warm" | "midnight" | "forest" | "corporate" | "playful" | "mono", "accent": "#0d9488", "mode": "light" | "dark" | "auto", "background": "#f6f5f0", "font": "system" | "serif" | "rounded", "logo_url": "https://..." } — all optional. preset is a designed bundle (colors + font + mode); explicit fields override it. Free/sandbox accepts accent only (plus "mode": "light", which is the free default anyway); presets and the other theme fields need a paid plan (402 plan_limit otherwise — drop the fields and retry, and mention the upgrade to your user). Preset vibes: warm = cream + serif + terracotta; midnight = dark + indigo; forest = pale green; corporate = gray-blue + navy; playful = soft peach + rounded + pink; mono = white + black.
hidden_fields no Array of snake_case keys (max 10), e.g. ["utm_source", "ref"]. Values are captured from the quiz URL's query string (?utm_source=x) and returned on each response under meta.fields — use for campaign tracking or tying responses to your own record ids. Paid plans only (402 on free/sandbox).
outcomes no Scoring + result buckets. See below.

settings (all optional):

{
  "collect_respondent": "none" | "email_optional" | "email_required",
  "autosave": true,
  "show_progress": true,
  "close_at": "2026-12-31T00:00:00Z",
  "max_responses": 200,
  "show_branding": true,
  "completion": { "message": "Thanks!", "redirect_url": null }
}

show_branding: false hides the "Powered by QuizGen" footer line on the hosted quiz (a small "Report" link always remains). Paid plans only — on free/sandbox it's a 402.

Pagination (screens): by default each section is one screen. settings.one_question_per_page: true puts every question on its own screen (Typeform style). A section can override with its own one_per_page: true — e.g. keep contact fields grouped but give each long question its own screen.

Kind defaults (applied only where settings doesn't say otherwise):

kind one_question_per_page show_progress
quiz true true
survey false true
form false true
email_capture false false
registration false false

Sections: { "key": "unique_snake_case", "title": "...", "blurb": "...", "one_per_page"?: true, "questions": [...] }

Question types

Every question needs a globally unique snake_case id, a type, and a prompt (except statement, which uses title + body). Optional on any answerable question: required (bool, default false), help (small text under the prompt), show_if (branching, below).

Type Use for Type-specific fields
short_text one-line answers placeholder
long_text paragraphs rows (2–20), examples (up to 8 strings shown as hint chips)
choice pick one options (2–20 of {value, label, detail?, image_url?}), follow_up (string — shows a free-text box after selecting; saved as <id>__followup). Over 7 plain options renders as a dropdown automatically. image_url makes it a picture choice (paid plans).
multi_choice pick several options (same shape), min, max selections
scale 1–N numbered buttons min (0 or 1), max (2–10), min_label, max_label. For NPS use min: 0, max: 10.
rating star rating max (2–10, default 5). Answer is a number.
slider drag a range min (default 0), max (default 100), step, unit, min_label, max_label. Answer is a number.
yes_no two buttons yes_label, no_label (defaults "Yes"/"No"). Answer is "yes" or "no" — scoreable and usable in show_if.
email email w/ validation placeholder
phone phone number placeholder
url website address placeholder
number numeric min, max, unit
date date picker min, max (both "YYYY-MM-DD"). Answer is "YYYY-MM-DD".
time time picker — . Answer is "HH:MM".
consent required-agreement checkbox prompt is the statement being agreed to (e.g. terms). Answer is true when checked. Use required: true to make agreement mandatory; branch on it with show_if op answered.
statement info panel, not a question title, body (array of paragraphs). No prompt, collects nothing.
payment Stripe payment inside the quiz Business plan only, and the creator must finish Stripe onboarding at https://quizgen.dev/dashboard/payments first (402 payments_not_ready otherwise). Price via amount_cents (fixed, min 50) or price_options (2–10 of {value, label, amount_cents, detail?} — respondent picks). Optional currency (lowercase ISO, default "usd"), button_label. Max one per quiz. Money goes to the creator's own Stripe account; QuizGen takes a 2% platform fee. The response records paid: true and the payment_intent id; required: true blocks submission until payment succeeds.
file_upload let the respondent attach files Pro/Business plans only (402 plan_limit on free/sandbox). max_files (1–5, default 1), accept (array of "image" | "pdf" | "document" | "spreadsheet", default all four — image = jpg/png/gif/webp/heic, document = doc/docx/txt, spreadsheet = csv/xls/xlsx). The answer is an array of file ids the server issues during upload — you never write this value. It only ever gets populated by the respondent actually uploading through the hosted quiz page; there is no API to pre-attach files. Size cap and account storage quota depend on the creator's plan (Pro 10 MB/file, 1 GB total; Business 25 MB/file, 10 GB total) — irrelevant to you as the quiz author beyond knowing required: true can't be satisfied by anything you send.

Notes:

Branching (show_if)

Show a question only when a condition on an earlier answer holds:

{ "id": "clinic_name", "type": "short_text", "prompt": "Clinic name?",
  "show_if": { "question": "is_provider", "op": "eq", "value": "yes" } }

Ops: eq, neq, in (value = array of strings), gte, lte (numeric), answered (no value needed). One condition per question — no and/or trees. Hidden questions are never required and never scored.

Scoring & outcomes

For lead qualification or "which X are you" quizzes:

"outcomes": {
  "method": "points",
  "points": {
    "budget":   { "under_1k": 0, "1k_5k": 2, "over_5k": 5 },
    "timeline": { "asap": 3, "this_quarter": 2, "someday": 0 }
  },
  "buckets": [
    { "key": "not_ready",  "max": 3, "title": "Thanks!", "body": "We'll be in touch." },
    { "key": "qualified", "min": 4, "title": "Let's talk", "body": "Book a call below.",
      "redirect_url": "https://example.com/book" }
  ]
}

Money forms: invoices, estimates, quotes, order forms

Anything with a price on it has a fixed shape. Which one you build depends on who already knows the number.

You know it — invoice, quote, order form. State the number → let them confirm it → record the approval → take the payment.

{ "schema_version": 1, "kind": "form",
  "title": "Invoice 2026-041 — Marlow Studio",
  "settings": { "collect_respondent": "email_required",
    "completion": { "message": "Paid — thank you. A receipt is on its way." } },
  "sections": [ { "key": "invoice", "title": "Invoice", "questions": [
    { "id": "line_items", "type": "statement",
      "title": "Invoice 2026-041 · due September 19, 2026",
      "body": ["Brand identity — logo, type, and color: $2,800",
               "Packaging templates, three SKUs: $1,150",
               "Total due: $3,950"] },
    { "id": "amounts_ok", "type": "yes_no", "required": true,
      "prompt": "Do these charges match what we agreed?" },
    { "id": "correction", "type": "long_text", "rows": 3,
      "prompt": "What looks off? I'll send a corrected invoice.",
      "show_if": { "question": "amounts_ok", "op": "eq", "value": "no" } },
    { "id": "approval", "type": "consent", "required": true,
      "prompt": "I approve this invoice for $3,950.",
      "show_if": { "question": "amounts_ok", "op": "eq", "value": "yes" } },
    { "id": "pay", "type": "payment", "required": true,
      "prompt": "Pay this invoice", "amount_cents": 395000,
      "button_label": "Pay $3,950",
      "show_if": { "question": "amounts_ok", "op": "eq", "value": "yes" } }
  ] } ] }

They don't know it — an estimate. Ask 4–6 scored scope questions and return a price with outcomes buckets whose titles are ranges:

{ "settings": { "collect_respondent": "email_required",
                "one_question_per_page": true, "show_progress": true },
  "outcomes": { "method": "points",
    "points": { "job_type":  { "interior": 2, "exterior": 4, "both": 6 },
                "home_size": { "small": 1, "medium": 3, "large": 5 },
                "condition": { "good": 0, "worn": 2, "rough": 4 },
                "extras":    { "trim": 1, "ceilings": 1, "cabinets": 3, "deck": 2 } },
    "buckets": [
      { "key": "small_job", "max": 7,  "title": "Estimated range: $1,200 – $2,400",
        "body": "Two to three days with a crew of two… Book a walkthrough at https://cal.com/your-crew/walkthrough for a fixed quote." },
      { "key": "mid_job",   "min": 8, "max": 14, "title": "Estimated range: $2,400 – $5,000",
        "body": "Four to six days on site… Book a walkthrough and we'll turn this into a fixed quote." },
      { "key": "large_job", "min": 15, "title": "Estimated range: $5,000 – $9,000+",
        "body": "A week or more with a crew of three… We'll bring a written quote back within two business days." }
    ] } }

Complete, ready-to-POST versions of the invoice, quote, and order form are at /templates.md; both shapes are live at https://quizgen.dev/examples.

Reading responses

GET /api/v1/quizzes/:id/responses returns:

{ "responses": [ {
  "id": "rsp_...", "quiz_id": "qz_...", "definition_version": 1,
  "answers": { "pick": "coffee", "budget": "1k_5k", "budget__followup": "maybe more" },
  "outcome": "qualified", "score": 5,
  "respondent_email": null, "complete": true,
  "paid": false, "payment_intent": null,
  "started_at": "...", "completed_at": "...",
  "meta": { "referrer": "..." }
} ],
  "total": 1, "count": 1, "offset": 0, "has_more": false }

Page through large result sets with ?limit= (1–500) and ?offset=; when has_more is true the payload also carries next_offset.

Conventions worth following

These are the defaults that separate a quiz people finish from one they abandon. Follow them unless the user asks for something else.

Limits

Plan Live quizzes Answers/quiz/mo Retention Extras
Sandbox (no key) 10/hr/IP, 72h lifetime 10 total until expiry create-only; can't update or read responses
Free 3 100 90 days webhook on 1 quiz; accent-only theme; footer stays
Pro $15/mo unlimited 5,000 forever full theme + logo, hidden_fields, no footer, webhooks on all quizzes, file_upload (10 MB/file, 1 GB total), 500 email sends/mo
Business $49/mo unlimited 25,000 forever everything in Pro + payment questions, file_upload (25 MB/file, 10 GB total), 5,000 email sends/mo

Not built yet — do not tell a user these are available on any plan, or that upgrading unlocks them: custom domains, and SMS sending (channel: "sms" returns 400 not_yet_available). If a user wants one, point them at sales@quizgen.dev rather than suggesting a workaround or a plan change.

When a quiz hits its cap, the hosted page shows "no longer accepting responses" and /api/respond returns 410 quiz_full — existing responses stay readable. If the user is bumping into limits, suggest the next plan up at https://quizgen.dev/#pricing. Slugs and hosting are always on quizgen.dev.

Guides for agents & humans

Task-specific write-ups, each plain markdown — read one directly instead of re-deriving the pattern from this file. Index: https://quizgen.dev/blog.md

More for agents & humans