Skip to content
FLORA DocsGo to app
Platform

Idempotency

Safely retry FLORA mutations without duplicating runs or assets.

Network errors happen. When your POST request fails mid-flight, you don’t know whether the server received it — retrying naively might create the same run twice. Idempotency keys make retries safe: the second request with the same key returns the original response instead of creating a duplicate.

  1. You generate a unique key for a logical operation: e.g. q3-thumbnail-DE-2024-09-15.
  2. You send the request with idempotency_key set to that key.
  3. FLORA stores (key → response) for 24 hours.
  4. If you retry within 24 hours with the same key + same body, FLORA returns the original response — no new side effect.

Use idempotency_key on any mutation whose accidental duplication would be expensive:

  • POST /techniques/{slug}/runs — duplicate runs charge credits twice.
  • POST /runs/generation and POST /runs/technique — same.
  • POST /assets — duplicate signed-URL reservations.
  • POST /projects — duplicate Projects.

Read endpoints (GET) and safely-repeatable actions (polling, listing) don’t need it.

const run = await client.techniques.runs.create('thumbnail-v3', {
inputs: [...],
mode: 'async',
idempotency_key: `q3-thumb-${marketCode}`,
});
run, err := client.Techniques.Runs.New(ctx, "thumbnail-v3", flora.TechniqueRunNewParams{
Inputs: inputs,
Mode: flora.TechniqueRunNewParamsModeAsync,
IdempotencyKey: param.Field("q3-thumb-" + marketCode),
})
Terminal window
flora techniques:runs create \
--technique-id thumbnail-v3 \
--input '{...}' \
--mode async \
--idempotency-key "q3-thumb-DE"
Terminal window
curl -X POST https://app.flora.ai/api/v1/techniques/thumbnail-v3/runs \
-H "Authorization: Bearer $FLORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [...],
"mode": "async",
"idempotency_key": "q3-thumb-DE"
}'

Good keys are deterministic from the operation, so the same logical call produces the same key:

  • q3-thumbnail-{market_code} — derived from the market you’re processing.
  • import-csv-row-{row_hash} — derived from a hash of the input row.
  • user-{user_id}-onboarding-thumbnail — derived from the user.

Bad keys are random per attempt, which defeats idempotency:

  • uuidv4() generated inside the retry loop — every retry gets a new key.
  • Date.now() — same problem.
  • "my-run" — collides across unrelated operations.

If you must use a UUID, generate it once at the start of the logical operation and reuse it across retries.

// Generate once per logical operation
const idempotencyKey = crypto.randomUUID();
async function createRunWithRetry() {
for (let attempt = 0; attempt < 3; attempt++) {
try {
return await client.techniques.runs.create('thumbnail-v3', {
inputs: [...],
mode: 'async',
idempotency_key: idempotencyKey, // same across retries
});
} catch (err) {
if (shouldRetry(err)) continue;
throw err;
}
}
}

FLORA compares the full request body byte-for-byte (after JSON normalization). If you retry with a different body — even a small change like reordering inputs — you get 422 idempotency_conflict:

{
"error": {
"code": "idempotency_conflict",
"message": "Idempotency key 'q3-thumb-DE' was used with a different request body."
}
}

Either reuse the original body or pick a new key.

  • Idempotency keys are remembered for 24 hours from the first successful request.
  • After 24 hours, a request with the same key creates a new run (fresh side effect).
  • Keys are scoped per API key — using the same key string from a different API key creates a fresh entry.

The exact HTTP response — status code, headers (including the original request-id), and body. A retry that hits the cache returns identical data, which is exactly the point: your code can treat the retry as a “first” success.

The cached response also includes the idempotent-replayed: true header so you can tell when a response came from the cache vs a fresh execution.

  • Not a deduplication system for output content. Two distinct keys with the same input create two distinct runs, each charged separately. Keys protect against accidental retries, not intentional duplicates.
  • Not a way to coalesce concurrent requests. Two concurrent requests with the same key may both execute (one will hit the cache shortly after; the other returns a fresh run). For strict singleton behavior, serialize the requests in your code.
  • Not authentication. Anyone with your API key can use any idempotency key — they don’t authenticate.
import Flora from '@flora-ai/flora';
import crypto from 'node:crypto';
const client = new Flora({ apiKey: process.env.FLORA_API_KEY });
function keyFor(row: { market: string; headline: string }) {
const hash = crypto
.createHash('sha256')
.update(`${row.market}|${row.headline}`)
.digest('hex')
.slice(0, 16);
return `q3-thumb-${row.market}-${hash}`;
}
async function importRow(row: { market: string; headline: string }) {
return client.techniques.runs.create('thumbnail-v3', {
inputs: [
{ id: 'headline', type: 'text', value: row.headline },
],
mode: 'async',
idempotency_key: keyFor(row),
});
}

Re-running this script with the same CSV is a no-op for already-processed rows. Edit a row’s headline and the key changes — that row gets re-run.

  • Errors422 idempotency_conflict and retry semantics.
  • Recipes — production-grade batch patterns.