Games are reachable from the Content API with the same operations the admin UI has, and at the same credit cost. The API routes through the very services the UI calls, so nothing about access, validation, or pricing differs between the two.
All routes live under /v1/content/classrooms/{classroomId}/games and take a tf_content_ key.
Resource operations
| Method | Path | Success response |
|---|---|---|
POST | /games | 201 with the created game |
GET | /games | 200 with a page of games |
GET | /games/{id} | 200 with the game, including its built HTML |
PATCH | /games/{id} | 200 with the updated game |
DELETE | /games/{id} | 200 with the deleted game |
POST | /games/{id}/versions/{version}/restore | 200 with the restored game |
POST /games honours the Idempotency-Key header, like the other content resources. A retried create with the same key returns the game made the first time rather than a second one.
Restoring a version runs no model and charges nothing. It makes the chosen version the newest rather than overwriting history, so a restore is itself reversible.
Reading a game returns its HTML
GET /games/{id} includes a content field holding the built game as a complete HTML document. A game is a self-contained page, so this is the whole thing, and it can be large.
GET /games does not include it. List responses carry metadata only, so paging through a classroom stays cheap. Fetch the content for the one game you actually need.
The contentKey field on a game is TutorFlow's internal storage reference. Treat it as opaque and read the game through the API rather than trying to address that key yourself.
Generation
Building a game is three steps, and each is its own call. They are separate because the plan is cheap and the build is not: reading a plan and rejecting it should not cost what building it costs.
| Method | Path | Credits |
|---|---|---|
POST | /games/{id}/brief | 1 |
POST | /games/{id}/build | 12 |
POST | /games/{id}/revise | 10 |
These are the same prices the admin UI pays, drawn from the organization's AI Credit balance. Check the balance with GET /v1/content/credits before a batch.
POST /games/{id}/brief accepts an optional referenceMarkdown body field to ground the plan in your own material. POST /games/{id}/revise takes a feedback field describing the change in plain language, the same way the chat panel in the editor does.
These three stream
The generation routes respond with text/event-stream, not a JSON body. Read them as Server-Sent Events. The build completes when the stream ends.
Disconnecting aborts the work. If your client hangs up mid-build, the model call stops rather than running to completion unread. That also means a client timeout shorter than a build will cancel the build, so set timeouts with the build duration in mind rather than the default.
A typical run
POST /gameswith a title, and keep the returned id.POST /games/{id}/briefwith what learners should practise. Read the plan from the stream.- Inspect the plan. If it is wrong,
PATCHthe game or ask for another brief. This is the cheap point to change your mind. POST /games/{id}/build. Wait for the stream to end.GET /games/{id}to read the built game and its version history.POST /games/{id}/revisewith feedback if something needs changing.PATCH /games/{id}withvisibility: "PUBLIC"when it is ready to share, and hand out its link.
What the build guarantees
Every generated version is checked twice before it is stored: an automated validation of the page, then a playthrough that opens the game and works the controls. A version that will not start, or that freezes partway, is rebuilt rather than returned.
Those checks confirm the game runs. They judge nothing about whether it suits a particular class, so an integration that publishes automatically is publishing something no person has played. See Games Overview for what the checks do and do not cover.