MakeQuestions API by Karson AI
Changelog
New updates and improvements to the MakeQuestions API and Karson AI API. Each release notes breaking changes, additions, and migration tips.
v3.6.0 — Jul 2026
Automatic question planning, live web research, and two new turn-level controls. question_config is now optional — omit it and the server plans the question set from your source material.
Added — auto question planning
- Omitting
question_configonPOST /generate-questionsnow runs the auto planner, which infers question types and counts from your source material. This reverses the hard requirement introduced inv3.1.0. - New
question_config.mode: "auto"shape for opting in explicitly (e.g. when you still want to pindifficulty). Sendingtype_countsalongsidemode: "auto"returns422. - Sending an explicit
"question_config": nullis still rejected with422— omit the key instead. - Manual
type_counts[type] = nullis unchanged: it keeps its selected-type, model-decided-count behavior and does not call the planner. - Responses now include
generation_plan—{ source, type_counts, difficulty, reasoning_summary? }— so auto and manual clients alike can see the effective plan.sourceisclient_exact/client_softfor manual configs,auto_default/auto_explicitfor planner runs, or anauto_fallback_*variant when the planner failed and defaults were used. - Streaming emits the plan as a new
planSSE event, before anyquestionevent.donerepeats it asgeneration_plan. - Two auto-planning controls on
/generate-questions:allowed_question_types(non-empty allowlist constraining the planner or a manual map) andmax_questions(positive cap on the total). They shipped with auto planning;v3.5.0records their removal from/follow-up.
Added — web_search and live web research
web_search: trueonPOST /generate-questionsandPOST /follow-up(defaultfalse, strictly opt-in) runs an internal research phase: live web search assembles a cited markdown source brief that becomes primary source material for generation.- Non-streaming responses gain
web_research:{ status, queries, sources: [{ n, url, title, published_date }], brief, brief_chars }. Thebriefis exposed in full — it is exactly what generation saw. - Streaming emits four new events before the intent's own stream:
web_search_started,web_research_progress({ stage: "searching", query }per search;{ stage: "source", url, title }per source),web_research_delta({ text }— the brief being written), andweb_research(the authoritative payload;donedoes not repeat it). - Questions from a web-search generation may carry
source_ids— the[n]markers from the brief they drew on. Best-effort hints, never a contract: unresolvable ids are dropped server-side, and the key is absent on non-web requests. - Research is chained from the turn's
response_idwhen one is present — on/follow-upand on/generate-questionscontinuation turns alike. - Research runs on its own model, independent of the
reasoningflag, soreasoning: falsekeeps its zero-reasoning-token contract. Research failure degrades to no-brief generation withweb_research.status: "failed"— it never fails the request. - Cost/latency adder when enabled: roughly +$0.02–0.05 and +4–10 s per request.
Added — question_history
POST /generate-questionsaccepts an optionalquestion_historyobject (items+omitted_count) describing questions your app already holds. Listed questions become an avoid-list in the generation prompt, and near-duplicate candidates are dropped.- Bounds: at most 200 items (more returns
422);questiontruncated to 300 chars,answerto 150,tagsto 20 × 64 chars,source_idsto 50 entries. Legacystatus: "pending"normalizes to"active". - It is not source material — a history-only request with no
response_id/context/attachments/image_urlsstill returns422. - Drops are reported in
agent_metadata.history_duplicate_dropsand, when streaming, indone.question_history. POST /follow-updoes not declare the field — sending it there is a422.
Added — ui_language
- Both turn endpoints accept an optional
ui_language("en" | "vi", default"en"). It sets the output language only when the input is language-ambiguous (e.g. a topic-only prompt); a clear content language in the source material always wins. - Unknown or invalid values — including explicit
null— are silently normalized to"en". No 4xx, so old and new clients interoperate.
Changed
POST /generate-questionsnow rejects unknown top-level keys with422(extra="forbid"), so a typo fails loudly instead of being ignored./follow-upalready did.POST /generate-questionsacceptsresponse_idto continue an existing question chain; continuation turns may omit source material. Origin turns still requirecontext,attachments, orimage_urls. (This landed alongside the auto-planning work and is whatv3.5.0builds on — the two shipped in the same rollout.)
Removed
POST /generate-questions/stream(raw model-JSON chunks) is deleted. It was mounted only when the server ran in dev mode and was never part of the public contract. UsePOST /generate-questionswith"streaming": true.
v3.5.0 — Jul 2026
Breaking. The more-questions follow-up intent is removed. POST /follow-up no longer accepts request_type: "more-questions" — it returns 422. Generate more questions in an existing thread by calling POST /generate-questions with the prior turn's response_id.
Breaking Changes
POST /follow-uprequest_typenarrows toinline-explanation | chat. Sending"more-questions"now returns422.POST /follow-upno longer acceptsquestion_config(orallowed_question_types/max_questions); those keys are rejected (extra="forbid"). Question-generation config belongs on/generate-questions.- The
MoreQuestionsResponseshape is retired — it was an alias of the/generate-questionsresponse. Question continuations return the standard/generate-questionsresponse. - One capability goes away entirely with the intent: starting a fresh chain from
question_configalone, with no source material.more-questionsallowed it;/generate-questionsdoes not. An origin turn still requirescontext,attachments, orimage_urls, so a request carrying onlyquestion_confignow returns422.
Migration
- Generate more questions in a thread: call
POST /generate-questionswith the prior turn'sresponse_idplus yourquestion_config, instead ofPOST /follow-upwithrequest_type: "more-questions". Continuation requests may omit source material — the chain carries prior context. - Were you calling
more-questionswith noresponse_id? That path is gone. Send real source material (context,attachments, orimage_urls) on the/generate-questionscall. POST /follow-upremains the endpoint forinline-explanationandchat.
Why
- One canonical endpoint per turn kind:
/generate-questionsowns question turns (fresh or continued viaresponse_id),/follow-upowns the text intents. Removes a redundant path and an empty response alias.
v3.4.0 — Jul 2026
reasoning now defaults to true on POST /generate-questions and POST /follow-up, and all generation runs on a single model. Breaking for any client that relied on the old implicit reasoning: false default.
Breaking Changes
reasoningnow defaults totrue. A request that omits the field runs the reasoning path: the response may includereasoning_summary(andreasoning_summary_part/reasoning_summarySSE events when streaming), and the call bills reasoning tokens.- To keep the previous instant behavior — no reasoning tokens, no
reasoning_summary— sendreasoning: falseexplicitly. - Clients that already send
reasoningexplicitly (trueorfalse) are unaffected.
Changed
- Single generation model across every path (generate, follow-up, inline-explanation, chat, streaming).
reasoning: trueuses low reasoning effort;reasoning: falseuses zero effort — truly instant, with no reasoning tokens (the prior default still spent a little). - Both modes are faster than the previous default: reasoning-mode and instant-mode latency both dropped in internal generation evals.
multiple_choicequestions are now guaranteed at least 2choices, enforced at decode time.
Why
- Reasoning mode measurably improves question quality — answer correctness, distractor quality, and LaTeX/JSON fidelity — and is now fast enough to be the default.
reasoning: falsebecomes an honest instant contract: zero reasoning tokens, no summary.
v3.3.0 — Jun 2026
Added file attachments (images and documents) as the preferred way to send files, and raised request limits.
Added
attachmentsonPOST /generate-questionsandPOST /follow-up: an array of{ "url", "mime_type", "name"? }with public HTTPS URLs. Images (image/jpeg,image/png,image/gif,image/webp) are sent as vision input; documents (application/pdf,.doc,.docx,text/plain) are read directly as source material — no need to pre-extract text. Up to 1,500 images and 50 documents per request; URLs must be HTTPS and ≤ 8,192 chars. Preferattachmentsover legacyimage_urls.
Changed
- Rate limit raised to 1,000 requests per minute per API key.
- Image input limit raised again, from 50 to 1,500 unique images per request. The 1,500 cap applies to the union of image
attachmentsand legacyimage_urlsafter de-duplication — not 1,500 of each.
Why
- Send PDFs, Word docs, and text files directly as question source material, instead of pre-extracting text.
- Higher limits support batch and document-heavy workloads.
v3.2.1 — May 2026
Relaxed the vision-input limits and started reporting them from /config.
Changed
image_urlson both endpoints: max unique URLs raised from 5 to 50, and max URL length from 2,048 to 8,192 characters.- Duplicate URLs are now removed before the max-image limit is enforced.
GET /confignow reports the active vision limits undervision.max_imagesandvision.max_url_length.
Migration
- No code changes required. Update your own client-side validation if you want to send more than 5 image URLs.
v3.2.0 — Apr 2026
Renamed the follow-up request field previous_response_id to response_id so request and response use the same name for the same handle. Breaking change for any client that calls POST /follow-up.
Breaking Changes
POST /follow-upnow expectsresponse_idin the request body.previous_response_idis no longer accepted and (becauseextra="forbid") returns422.- Update payloads from
{ "previous_response_id": "resp_abc123", ... }to{ "response_id": "resp_abc123", ... }. - Response bodies and SSE
doneevents are unchanged — they already usedresponse_id.
Why
- One name for the continuation handle on both ends of a turn — copy a
response_idstraight from the last response into the next request. - Removes the long-standing wording mismatch in docs and SDKs.
v3.1.0 — Apr 2026
Simplified the request contract: question_config is now required and the "mixed" question type is gone. Callers declare exactly which concrete types they want and optionally how many of each.
Partly superseded by v3.6.0. question_config is optional again — omitting it runs the auto planner rather than returning 422. Everything below about type_counts semantics and the removal of "mixed" still holds.
Breaking Changes
question_configis required onPOST /generate-questionsand onPOST /follow-upwithrequest_type: "more-questions". Requests without it return422. (Relaxed inv3.6.0— omitting it now triggers auto planning.)type_countsmust be a non-empty map of concrete question types. Empty maps, all-0maps, unknown keys, and the old"mixed"key are all rejected with422. (A0value on an individual key is silently dropped — equivalent to omitting the key — as long as at least one entry remains.)- No more implicit "open mix" default. Omit a type to exclude it; use
nullper-type to let the model decide that type's count (the server guarantees at least 1 of every requested type). - Playground no longer offers a "Mixed" type option — pick one or more concrete types.
Why
- Smaller system prompts — documentation for unused types is no longer injected.
- Predictable count enforcement — the validator and agentic filler always know exactly which types to target.
- Simpler mental model — one required, non-empty map with clear per-key semantics (
int= exact count,null= model decides quantity with a guaranteed minimum of 1 of that type, omitted = disabled).
v2.9.1 — Apr 2026
Added optional vision input via image_urls on the generation endpoints.
Added
- Optional
image_urlsonPOST /generate-questionsandPOST /follow-up(allrequest_typevalues). Image URLs are forwarded to the model alongside the text input on the same turn.
Validation
- HTTPS URLs only.
- Max 5 URLs per request; each URL ≤ 2,048 characters. (Count raised to 50 in
v3.2.1and to 1,500 inv3.3.0; URL length raised to 8,192 inv3.2.1and unchanged since.) - Duplicates are removed server-side.
v2.9.0 — Mar 2026
Generation requests adopt question_config. The legacy top-level shape (question_type_counts, top-level difficulty, allowed_types) is no longer accepted on POST /generate-questions.
Breaking Changes
- Top-level
question_type_counts,difficulty, andallowed_typesare removed fromPOST /generate-questions. Usequestion_configwith atype_countsmap and optionaldifficulty. - Same rules apply to
POST /follow-upwithrequest_type: "more-questions". - Do not send
"mixed"as a difficulty value — usenullor omit.
Behavior
- Omitting
question_config(or sendingnull) used the default open mix at this release. This was tightened to a hard requirement inv3.1.0, then reopened inv3.6.0as model-driven auto planning (an explicitnullis still rejected — omit the key).
v2.8.1 — Jan 2026
Streaming inline-explanation now emits text deltas as they arrive, so UIs can render the explanation character-by-character instead of waiting for the full payload.
Added
- New SSE event
explanation_texton streamingPOST /follow-upwithrequest_type: "inline-explanation". Data shape:{ "text": "<delta>" }. - The existing
explanationevent still fires once after streaming completes with the full text — backward-compatible for clients that only consume the final payload.
Fixed
- Analytics:
delivery_modeis now populated forinline-explanationrequests (was previouslyNULL); failedinline-explanationandchatrequests are now persisted with error metadata.
v2.8.0 — Jan 2026
Expanded POST /follow-up to three request types — more-questions, inline-explanation, and chat — enabling conversational interactions on top of question generation.
Added
request_type: "inline-explanation"— concise explanation for one question. Put the question text ininput;question_type_countsis ignored.request_type: "chat"— free-form tutoring chat over the thread; always streamed via SSE.response_idreturned on every/follow-upresponse (and on the SSEdoneevent) for chaining further turns. (At this release the corresponding request field was still namedprevious_response_id— seev3.2.0.)
Validation
more-questions:question_type_countsrequired;previous_response_idoptional.inline-explanation:previous_response_idandinputrequired;question_type_countsignored.chat:previous_response_idandinputrequired; always streamed.
v2.7.0 — Jan 2026
Consolidated streaming endpoints into a single endpoint with a streaming parameter.
Breaking Changes
/generate-questions/stream-by-questionremoved — Use/generate-questionswithstreaming: true.
Migration
- Before:
POST /generate-questions/stream-by-question - After:
POST /generate-questionswith"streaming": truein request body
New Parameter
streaming— Boolean parameter to enable SSE streaming mode (default:false).- When
true, returns questions one at a time via Server-Sent Events. - When
false(default), returns all questions at once (batch mode).
Improvements
- Single endpoint for both batch and streaming modes.
- Consistent API pattern matching the follow-up endpoint.
- Simplified client integration — same endpoint, just add a parameter.