Skip to main content
POST
Generates a banner image for a unit of learning, used as the unit’s cover in the planner and library.
This endpoint takes a unit_id, not a subject and topic. It reads the unit’s subject, topic and year_group from the database itself, so the caller cannot vary the prompt.

Request

string
required
The unit to generate a cover for. Must belong to the calling user — this is one of the few endpoints that checks per-row ownership and returns 403 when it fails.

Response

boolean
true when a URL is available, whether generated or served from cache.
string
Public URL of the banner in the unit-hero-images bucket.
"row" | "content" | false
Which of the three paths served the request. See below.

Caching

cached tells you which of three paths served the response, and the distinction matters because only one of them costs a generation: The cache key is a SHA-256 over subject, topic and year_group — normalised for case and whitespace — salted with a composition version. Two units with the same three values share one stored object rather than generating twice.
Bumping the composition version invalidates every key at once. Existing units keep serving their old banner until next viewed, then lazily regenerate in the new composition. Old objects are left in place rather than deleted.

Errors

If the image uploads but the units row fails to update, the call still returns 200 with the URL — the image is in storage and usable, only the row is stale. That unit will regenerate its key on next view and land on the "content" cache path, so the failure costs a lookup rather than a second generation.