---
title: Generated Assets quickstart
description: Generate an image from a recipe, lock it, and fetch it from its public URL in three calls with one key.
type: tutorial
product: assets
summary: A recipe, generated variants, a committed lock, and a served image
updated: 2026-10-07
order: 1
---

This takes you from a recipe to an image served from its public URL, using
only a key and `curl`. Nothing is deployed: the asset lives in Sylphx
Generated Assets, and the same calls run in CI.

<Prerequisites
	items={[
		'An account, and a project with an environment',
		'A secret key holding the assets:write and assets:read scopes, as SYLPHX_API_KEY',
		'curl and jq',
	]}
/>

The examples use the environment `orgs/acme/projects/shop/envs/production`;
use your own. `sylphx access whoami` prints the environment your key belongs
to, and a key only reaches its own environment.

```bash
export ENV=orgs/acme/projects/shop/envs/production
```

## 1. Declare and generate

A recipe is the asset's kind, prompt and parameters. Here, a 1024 by 768
banner in two variants to choose from.

**cURL**

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/assets:generate" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipes": [{
        "kind": "image",
        "prompt": "A watercolour harbour at dawn, wide banner, no text",
        "params": {"size": "1024x768", "variants": "2"}
      }]}' > generate.json
jq '{created, generated, pending, failed, refused}' generate.json
export KEY=$(jq -r '.assets[0].key' generate.json)
```

**TypeScript**

```ts
import { Sylphx } from '@sylphx/sdk'

const sylphx = new Sylphx()
const { env } = await sylphx.access.whoami({})
const recipes = [
  {
    kind: 'image',
    prompt: 'A watercolour harbour at dawn, wide banner, no text',
    params: { size: '1024x768', variants: '2' },
  },
]
let res = await sylphx.assets.assets.generate({ parent: env, recipes })
while (res.pending > 0) res = await sylphx.assets.assets.generate({ parent: env, recipes })
const key = res.assets[0].key
```

`assets[0]` is the summary of your recipe: its `key`, its resource `name`, its
`state`, and how many variants are ready of how many wanted. A generate works
within a bounded time; while `pending` is above 0, send the same request
again. A recipe that already has its variants generates nothing. Parameters
are `size` (`WIDTHxHEIGHT`, 64 to 2048 pixels, default `1024x1024`) and
`variants` (1 to 8, default 1). `"validate_only": true` checks the recipes and
returns the counts without generating.

## 2. Look at the variants

```bash
curl -sS "https://api.sylphx.com/v1/$ENV/assets/$KEY" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  | jq '{state, variants: [.variants[] | {slot, content_hash, url, width, height}]}'
```

Each variant has a `url`. Open it, or fetch it:

```bash
curl -sS -o banner.png "$(curl -sS "https://api.sylphx.com/v1/$ENV/assets/$KEY" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" | jq -r '.variants[0].url')"
```

The URL is public and immutable, and caches for a year. To pick one variant,
approve it; to replace one, reject it and generate again:

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/assets/$KEY:approveVariant" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content_hash": "<content_hash>"}'
```

## 3. Lock

Ask for the lock of the keys your code declares, and commit it.

**cURL**

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/assets:lock" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"keys\": [\"$KEY\"]}" > lock.json
jq '{missing, incomplete}' lock.json
jq -r .content lock.json > assets.lock
```

**TypeScript**

```ts
import { writeFileSync } from 'node:fs'

const lock = await sylphx.assets.assets.lock({ parent: env, keys: [key] })
writeFileSync('assets.lock', lock.content)
```

The lock holds only the keys you name, so a recipe you remove drops out of it.
`missing` lists keys never generated and `incomplete` keys whose variants are
not all ready; a CI step that fails on either keeps an unlocked asset out of a
release. The file's format is published at
`https://sylphx.com/schema/assets/lock-1.json`.

## Read it back

```bash
curl -sS "https://api.sylphx.com/v1/$ENV/assets:export" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" | jq '{items: (.items | length), next_page_token, manifest_digest}'
```

Every servable variant in the environment, as one paged manifest (follow
`next_page_token`); the last page carries a `manifest_digest`, the SHA-256 of
every item's `content_hash`, so a client can prove it holds the whole set.

## Next

<RelatedDocs
	links={[
		{
			href: '/docs/assets',
			label: 'Generated Assets',
			description: 'Recipes, keys, the lock, and review.',
		},
		{
			href: '/docs/api/assets',
			label: 'The assets collection',
			description: 'Every method, its scope and its examples.',
		},
		{
			href: '/docs/platform/keys-and-scopes',
			label: 'Keys and scopes',
			description: 'Secret and publishable keys, and what each may do.',
		},
	]}
/>
