Skip to content
FLORA DocsGo to app

Generate a grid

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.

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);
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)
}
}
#!/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

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>
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');
  • 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.
  • 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.
  • 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 with idempotency keys.