Zum Inhalt springen
Floweet
Profi-Zugang

Idempotent POST requests

The Idempotency-Key header makes every POST safe to retry without duplicates for 24 hours: replays, requests in progress, reused keys and retries.

api.floweet.com/v1 · Contract 1.0.0-beta.1 · Beta

This documentation is written in English and French; it is shown to you in English.

Sections · Idempotency

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:write
Request
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 instance 6f1c2b0e-8d4a-4f7e-9a51-3c2d1e0f9b87). A key in another format answers 400 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: 429 and 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. GET requests do not need it; PUT, PATCH and DELETE are 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));
  }
}