---
title: Generated Assets
description: Declare the images your app needs as recipes in code; each is generated once, locked by content hash, and served from an immutable public URL.
type: tutorial
product: assets
summary: What an asset is, how generate and lock work, and where to go next.
updated: 2026-10-07
order: 0
---

An [Asset](/docs/api/assets) is one image your code declares as a recipe: a
kind, a prompt and a few parameters. `:generate` makes the variants the recipe
asks for, once; `:lock` returns the `assets.lock` file you commit, which pins
every asset to the exact files that were made; each file is served from an
immutable URL named by its content hash. There is nothing to deploy and no
image model to sign up with: one key in your project is enough.

## What an asset gives you

- **The recipe is the identity.** An asset's id is a 16-hex key computed from
  its recipe, so the same recipe is always the same asset, and generating it
  again costs nothing. Change the prompt and it is a new asset.
- **Generated once, then locked.** The lock names each variant's SHA-256, so a
  build ships exactly the files you reviewed, on every machine.
- **Served by content hash.** Each variant's `url` is public, immutable and
  cacheable for a year; a new file is a new URL, so there is nothing to purge.
- **Review in the API.** Approve a variant to record who chose it; reject one
  and the next `:generate` fills its slot; retire one and its URL answers 404.
- **Bounded calls.** A generate works within a fixed time; call it again until
  `pending` is 0. It is idempotent, so a CI retry is safe.
- **Images today.** Recipes of kind `image` are generated now, 64 to 2048
  pixels a side and 1 to 8 variants. Other kinds (`sfx`, `music`, `video`,
  `character`, `voice`) and images drawn from a character are recorded and
  answer `unsupported` until they are built.

## Names and ids

<KeyValue
	items={[
		{ key: 'Asset', value: 'orgs/{org}/projects/{project}/envs/{env}/assets/{key}', mono: true },
		{ key: 'Asset key', value: '16 lowercase hex characters, from the recipe', mono: true },
		{ key: 'Asset id', value: 'ast_<cell><ulid>', mono: true },
		{ key: 'Variant', value: 'its content_hash, 64 lowercase hex characters', mono: true },
	]}
/>

## The scopes

- `assets:write` — `:generate`, and approving, rejecting and retiring a
  variant.
- `assets:read` — reading assets, `:lock` and `:export`.

Generation is billed per image stored; an asset that already exists is not
generated again. [Keys and scopes](/docs/platform/keys-and-scopes) covers the
two kinds of key.

<RelatedDocs
	links={[
		{
			href: '/docs/assets/quickstart',
			label: 'Quickstart',
			description: 'Declare a recipe, generate it, lock it, and fetch the image.',
		},
		{
			href: '/docs/api/assets',
			label: 'The assets collection',
			description: 'Every method, field and example.',
		},
	]}
/>
