---
title: Generate a grid | FLORA API
description: Run a Technique with N outputs and render them as a grid.
---

The most common FLORA API workflow: take a brief, run a Technique, get back multiple variants. The “3×3 grid” is the canonical shape.

**What you’ll build:** a script that runs `thumbnail-v3` with a brief and returns 9 image URLs.

## TypeScript

```
import Flora from '@flora-ai/flora';


const client = new Flora({ apiKey: process.env.FLORA_API_KEY });


async function generateGrid(brief: string, count = 9) {
  const technique = await client.techniques.retrieve('thumbnail-v3');
  console.log(`Cost per output: $${technique.run_cost.toFixed(2)} × ${count}`);


  const run = await client.techniques.runs.create('thumbnail-v3', {
    inputs: [
      { id: 'prompt', type: 'text', value: brief },
      { id: 'count', type: 'text', value: String(count) },
    ],
    mode: 'async',
  });


  return pollUntilDone(run.run_id, 'thumbnail-v3');
}


async function pollUntilDone(runId: string, slug: string) {
  while (true) {
    const result = await client.techniques.runs.retrieve(runId, { techniqueId: slug });
    if (result.status === 'completed') return result.outputs.map((o: any) => o.url);
    if (result.status === 'failed') throw new Error(result.error_message);
    await new Promise((r) => setTimeout(r, 2000));
  }
}


const urls = await generateGrid(
  'Smart living, simple. Audience 25-40, design-conscious. Warm minimalism, soft shadows. No clip-art, no gradients.'
);


console.log(urls);
```

## CLI

```
#!/usr/bin/env bash
set -euo pipefail


BRIEF="Smart living, simple. Warm minimalism."
COUNT=9


RUN_ID=$(flora techniques:runs create \
  --technique-id thumbnail-v3 \
  --input "{id: prompt, type: text, value: '$BRIEF'}" \
  --input "{id: count, type: text, value: '$COUNT'}" \
  --mode async \
  --jq '.run_id' -r)


while true; do
  STATUS=$(flora techniques:runs retrieve \
    --technique-id thumbnail-v3 \
    --run-id "$RUN_ID" \
    --jq '.status' -r)


  case "$STATUS" in
    completed) break ;;
    failed)    echo "run failed"; exit 1 ;;
    *)         sleep 2 ;;
  esac
done


flora techniques:runs retrieve \
  --technique-id thumbnail-v3 \
  --run-id "$RUN_ID" \
  --jq '.outputs[].url' -r
```

## Display the grid

In a Node/browser app, render the URLs as an HTML grid:

```
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px' }}>
  {urls.map((url) => (
    <img key={url} src={url} alt="" loading="lazy" />
  ))}
</div>
```

## Save the grid as files

```
import fs from 'node:fs/promises';
import path from 'node:path';


async function downloadAll(urls: string[], dir: string) {
  await fs.mkdir(dir, { recursive: true });
  await Promise.all(
    urls.map(async (url, i) => {
      const res = await fetch(url);
      const buf = Buffer.from(await res.arrayBuffer());
      await fs.writeFile(path.join(dir, `tile_${i}.png`), buf);
    })
  );
}


await downloadAll(urls, './out/q3-grid');
```

## Variations

- **Aspect ratio**: many Techniques accept `aspect_ratio` as an input. Add `{ id: 'aspect_ratio', type: 'text', value: '1:1' }`.
- **Reference image**: pass an image URL as a Technique input — see [Iterate on outputs](/recipes/iterate-on-outputs/index.md).
- **Different counts**: not all Techniques support N>1. Check `technique.outputs` shape after `retrieve` — if it returns a single output, run the Technique N times in parallel instead.

## Tips

- Pre-check the cost with `retrieve` before kicking off — total = `run_cost × count` in USD (or `run_cost × N runs` for serial calls).
- Output URLs are long-lived but not permanent. Download anything you need to keep.
- For batches >10, switch to a [batch pattern](/recipes/batch-from-csv/index.md) with idempotency keys.

## Related

- **[Iterate on outputs](/recipes/iterate-on-outputs/index.md)** — re-run with chosen outputs as references.
- **[Upload an asset](/recipes/upload-an-asset/index.md)** — use your own image as a Technique input.
- **[Batch from a CSV](/recipes/batch-from-csv/index.md)** — many runs across structured input.
