On this page
A network cut or a timeout does not tell you whether your request went through. To retry without creating the same prospect or simulation twice, add the Idempotency-Key header to every POST.
Example
POST
/v1/prospectsScope prospects:writeRequest
curl -X POST "https://api.floweet.com/v1/prospects" \
-H "Authorization: Bearer $FLOWEET_API_KEY" \
-H "Idempotency-Key: 6f1c2b0e-8d4a-4f7e-9a51-3c2d1e0f9b87" \
-H "Content-Type: application/json" \
-d '{
"name": "Transports Martin",
"fleet": {
"fuel": "gazole",
"currentBrand": "Esso",
"reference": "cheapest",
"kmPerVehicle": 30000,
"lines": [
{
"type": "lcv",
"vehicles": 12,
"consumptionL100": 8.5
},
{
"type": "hgv",
"vehicles": 3,
"consumptionL100": 30
}
]
}
}'Response 201
{
"id": "33e3daba-2998-438d-a05e-1c513009107a",
"name": "Transports Martin",
"updatedAt": "2026-10-26T11:46:19.310433+00:00",
"input": {
"fuel": "gazole",
"currentBrand": "Esso",
"reference": "cheapest",
"kmPerVehicle": 30000,
"lines": [
{
"type": "lcv",
"vehicles": 12,
"consumptionL100": 8.5
},
{
"type": "hgv",
"vehicles": 3,
"consumptionL100": 30
}
]
},
"cost": {
"fuel": "gazole",
"pricesFrom": "2026-10-19T11:35:43.000Z",
"pricesTo": "2026-10-26T11:00:00.000Z",
"vehicles": 15,
"currentBrand": "Esso",
"currentPrice": 2428,
"referenceBrand": "Total Access",
"referencePrice": 2252,
"lines": [
{
"type": "lcv",
"vehicles": 12,
"consumptionL100": 8.5,
"litersPerYear": 30600,
"overspendPerYear": 538600,
"overspendPerVehicle": 44900,
"co2Tonnes": 75.9,
"ets2PerYear": 341500
},
{
"type": "hgv",
"vehicles": 3,
"consumptionL100": 30,
"litersPerYear": 27000,
"overspendPerYear": 475200,
"overspendPerVehicle": 158400,
"co2Tonnes": 67,
"ets2PerYear": 301300
}
],
"total": {
"litersPerYear": 57600,
"overspendPerYear": 1013800,
"co2Tonnes": 142.8,
"ets2PerYear": 642800
},
"replaceFirst": "hgv",
"ets2": {
"startYear": 2028,
"pricePerTonne": 4500,
"costPerLiter": 112
}
}
}Sent again with the same key and the same body, the request returns the same 201 response, with the Idempotent-Replayed: true header: no second prospect is created.
Rules
- One key per operation: 1 to 255 characters among letters, digits,
._:-, preferably a UUID v4 (for instance6f1c2b0e-8d4a-4f7e-9a51-3c2d1e0f9b87). A key in another format answers400 validation_error. Generate it before the first attempt and keep it with the operation (in your database, in your job queue) for all its retries. - For 24 hours, the same key with the same request (same method, same path, same parameters, same body) replays the first response as it was, with
Idempotent-Replayed: true. - The same key with another request:
422 idempotency_key_mismatch. The same request still in progress:409 idempotency_in_progress, retry in a second. - Stored and replayed responses: successes (2xx) and 4xx errors, except
429. Not stored, so that a retry really runs:429and 5xx errors — the key is released. - Keys belong to your organization and environment: all your API keys of one environment share them, and a key used in test never replays a live response.
- The header is optional, but strongly recommended on every
POST.GETrequests do not need it;PUT,PATCHandDELETEare already safe to repeat (sent twice, they leave the resource in the same state) and ignore it.
Reused key
Response 422
{
"error": {
"code": "idempotency_key_mismatch",
"message": "This idempotency key was already used for a different request (method, path or body). Use a new key."
}
}Retry with the same key
import { randomUUID } from "node:crypto";
// One key per operation, created BEFORE the first attempt and kept for all its retries.
const idempotencyKey = randomUUID();
const delays = [1_000, 5_000, 30_000];
async function createProspect(body) {
for (let attempt = 0; ; attempt++) {
let res;
try {
res = await fetch("https://api.floweet.com/v1/prospects", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FLOWEET_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
} catch (networkError) {
res = null; // Network cut or timeout: we do not know whether the request went through.
}
if (res?.ok) return res.json(); // 201, or the replay of the first response (Idempotent-Replayed: true)
const retryable = !res || res.status >= 500 || res.status === 429 || res.status === 409;
if (!retryable || attempt >= delays.length) throw new Error(res ? (await res.json()).error.code : "network_error");
const wait = Number(res?.headers.get("Retry-After") ?? 0) * 1000 || delays[attempt];
await new Promise((r) => setTimeout(r, wait));
}
}