--- 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.runCost} credits × ${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.runId, '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.errorMessage); 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); ``` ## Go ``` package main import ( "context" "fmt" "log" "os" "time" "github.com/florafauna-ai/flora-go" "github.com/florafauna-ai/flora-go/option" ) func generateGrid(ctx context.Context, c *flora.Client, brief string, count int) ([]string, error) { run, err := c.Techniques.Runs.New(ctx, "thumbnail-v3", flora.TechniqueRunNewParams{ Inputs: []flora.TechniqueRunNewParamsInput{ {ID: "prompt", Type: "text", Value: brief}, {ID: "count", Type: "text", Value: fmt.Sprint(count)}, }, Mode: flora.TechniqueRunNewParamsModeAsync, }) if err != nil { return nil, err } for { result, err := c.Techniques.Runs.Get(ctx, run.RunID, flora.TechniqueRunGetParams{ TechniqueID: "thumbnail-v3", }) if err != nil { return nil, err } switch result.Status { case "completed": urls := make([]string, 0, len(result.Outputs)) for _, o := range result.Outputs { urls = append(urls, o.URL) } return urls, nil case "failed": return nil, fmt.Errorf("run failed: %s", result.ErrorMessage) } time.Sleep(2 * time.Second) } } func main() { ctx := context.Background() client := flora.NewClient(option.WithAPIKey(os.Getenv("FLORA_API_KEY"))) urls, err := generateGrid(ctx, client, "Smart living, simple. Warm minimalism.", 9) if err != nil { log.Fatal(err) } for _, u := range urls { fmt.Println(u) } } ``` ## 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 '.runId' -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: ```
{urls.map((url) => ( ))}
``` ## 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 — cost = `runCost × count` (or `runCost × 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.