Appearance
Quizzes
Manage quizzes, or generate new ones with AI.
| Operation | Endpoint | Scope |
|---|---|---|
| List quizzes | GET /api/v1/quizzes | api:quiz:read |
| Create a quiz | POST /api/v1/quizzes | api:quiz:write |
| Get a quiz | GET /api/v1/quizzes/{quizId} | api:quiz:read |
| Replace a quiz | PUT /api/v1/quizzes/{quizId} | api:quiz:write |
| Update a quiz | PATCH /api/v1/quizzes/{quizId} | api:quiz:write |
| Delete a quiz | DELETE /api/v1/quizzes/{quizId} | api:quiz:write |
| Generate a quiz | POST /api/v1/quizzes/generate | api:quiz:write |
| Poll an async generation | GET /api/v1/quizzes/generate/{requestId} | api:quiz:read |
| List a quiz's assignments | GET /api/v1/quizzes/{quizId}/assignments | api:assignment:read |
List quizzes
GET /api/v1/quizzesjson
{
"data": [
{ "id": "…", "content": { "…": "…" }, "image_url": null,
"created_at": "…", "updated_at": "…" }
]
}Create a quiz
POST /api/v1/quizzes| Field | Type | Required | Notes |
|---|---|---|---|
content | object | Yes | Quiz content (questions, options, answers). |
image_url | string | No | Cover image URL. |
content is validated against the quiz rules; invalid content returns 400. The number of questions must fit your plan's per-quiz limit, else 400 with code: "QUESTION_LIMIT_EXCEEDED".
json
{ "data": { "id": "…" } } // 201Get / replace / update / delete
GET /api/v1/quizzes/{quizId} → { "data": { …quiz } } (404 if not found)
PUT /api/v1/quizzes/{quizId} → body { content, image_url? } → { "data": { "id": "…" } }
PATCH /api/v1/quizzes/{quizId} → body { content?, image_url? } → { "data": { "id": "…" } }
DELETE /api/v1/quizzes/{quizId} → { "data": { "deleted": true } }PUT replaces content (and image when supplied); PATCH updates only the fields present.
Generate a quiz
POST /api/v1/quizzes/generateGenerates a quiz with AI, saves it, and returns its ID. Runs non-streaming and does not produce a cover image.
| Field | Type | Notes |
|---|---|---|
targetLanguage | "ja" | "zh-TW" | Quiz language. |
sourceMode | "text" | "image" | "mixed" | "instruction" | Defaults to text. |
textSource | string | Required for text / mixed. |
imageUrl / imageUrls | string / string[] | Required for image / mixed. Public image URLs. |
proficiencyLevel | string | JLPT (N5–N1), TOCFL, or CEFR level. |
difficulty | "easy" | "medium" | "hard" | Difficulty within the level. |
teacherInstructions | string | Optional steering text. |
topic | string | Optional topic for instruction mode. |
json
{
"data": {
"quiz_id": "…",
"title": "…",
"question_count": 10,
"target_language": "ja",
"tokens_used": 1234
},
"requestId": "…"
}A 402 with upgrade_url indicates your generation quota is exhausted.
Asynchronous generation
Generation can take tens of seconds. To avoid client/proxy timeouts on slow generations, set async: true (or mode: "async") in the request body. The endpoint validates the request, enqueues a background job, and returns 202 immediately with a request_id — it does not wait for the quiz:
json
{ "data": { "request_id": "…" } } // 202Validation still happens synchronously, so an invalid request fails fast with the same 4xx it would return in sync mode (nothing is enqueued).
Then poll for the result:
GET /api/v1/quizzes/generate/{requestId} // scope: api:quiz:readjson
{ "data": { "status": "processing", "request_id": "…" } }
{ "data": { "status": "success", "request_id": "…", "quiz_id": "…",
"question_count": 10, "target_language": "ja", "tokens_used": 1234 } }
{ "data": { "status": "failed", "request_id": "…", "error": "…" } }Poll until status is success or failed. An unknown requestId (or one belonging to another account) returns 404.
List a quiz's assignments
GET /api/v1/quizzes/{quizId}/assignments?page=1&limit=20&status=Lists the assignments created under a quiz you own (same response shape and paging as GET /api/v1/assignments). Returns 404 if the quiz does not exist or is not yours.
| Query | Default | Notes |
|---|---|---|
page | 1 | 1–1000. |
limit | 20 | 1–100. |
status | — | Filter by status. |