BlendFi

Idempotency

Make your POSTs and PATCHes safe to retry — no duplicate users, no double-charges, even when the network drops mid-call.

Reliability

Idempotency

An idempotency key is like a sticky note attached to your request. If your network drops in the middle of a POST /v1/transactions and you don't know whether the transaction was created, you retry with the same key — and BlendFi returns the same response instead of creating a second transaction. No duplicates. No double-charges.

Every POST and PATCH in the BlendFi API requires an Idempotency-Key header. This guide tells you how to use it correctly.

At a glance

One key per logical operation

Generate a fresh UUID per intent (one quote, one user creation, one transaction). Reuse the same key across retries of that intent.

Retry with the same key, get the same response

Same key + same body returns the original response — exact status, exact JSON. The operation is never executed twice.

Different body with the same key is rejected

If you accidentally reuse a key for a different request, you get `409 idempotency_key_reused`. This protects you from your own bugs.

How it works

1. Generate a key per intent

Mint a unique string before you send the request. UUID v4 is the recommended format — anything random and unpredictable works.

import { randomUUID } from "node:crypto";

const idempotencyKey = randomUUID();
// "0196c5d9-2e34-7c24-a47e-a0e1f89bb8a9"

The key represents one logical operation, not one network attempt. If you retry, reuse the same key.

2. Send the key on every POST and PATCH

Add it to every mutating request as the Idempotency-Key header:

curl -X POST $BLENDFI_BASE/v1/transactions \
  -H "Authorization: Bearer $BLENDFI_KEY" \
  -H "Idempotency-Key: 0196c5d9-2e34-7c24-a47e-a0e1f89bb8a9" \
  -H "content-type: application/json" \
  -d '{"quote_id": "01J..."}'

If you forget the header on a POST or PATCH, BlendFi rejects with 400 idempotency_key_required. GET, DELETE, and other read-only methods don't need it.

3. Retry with the same key on failure

When a request times out, the connection drops, or you get a 5xx — retry with the same key and same body. BlendFi will:

  • If the original request succeeded, return the same response (same status, same body) without re-executing.
  • If the original request is still in flight (less than 60 seconds old), return 409 idempotency_key_in_progress. Wait and retry.
  • If the original request failed with a 5xx, re-execute the operation. 5xx results are never cached, because the failure means we don't know whether the operation actually happened.
async function createTransaction(quoteId, key) {
  for (let attempt = 0; attempt < 4; attempt++) {
    const res = await fetch(`${BASE}/v1/transactions`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${KEY}`,
        "Idempotency-Key": key,
        "content-type": "application/json",
      },
      body: JSON.stringify({ quote_id: quoteId }),
    });

    if (res.status >= 500) {
      await sleep(2 ** attempt * 250);
      continue;
    }
    return res.json();
  }
  throw new Error("transaction failed after retries");
}

What stays the same across retries

When BlendFi replays a stored response, everything matches the original:

First callRetried call (same key)
HTTP status201 Created201 Created
Response body{"id":"01J...","status":"pending",...}{"id":"01J...","status":"pending",...} (byte-for-byte)
Side effectsTransaction created, audit event emittedNone — no second transaction, no second audit event
Response headersOriginalOriginal (minus content-length, recomputed)

This means your retry logic can treat a replayed response identically to the original. You don't need to detect "this is a replay" — just parse it.

Retry rules at a glance

The 4xx vs 5xx rule

Errors in the 4xx range (validation failures, conflicts, missing capabilities) are cached. Retrying with the same key will return the same 4xx — fix your request and use a new key.

Errors in the 5xx range (BlendFi-side failures) are not cached. Retrying with the same key re-executes the operation. This is intentional: a 5xx means we don't know if the operation succeeded, so we keep the key live for retries.

Original outcomeWhat happens on retry with same key
2xx successReturns cached response. No re-execution.
4xx client errorReturns cached error. Use a new key after fixing.
5xx server errorRe-executes. Retry is safe.
Still in flight (under 60 s)Returns 409 idempotency_key_in_progress. Wait.
Different body, same keyReturns 409 idempotency_key_reused. Bug — investigate.

Common pitfalls

Reusing one key across multiple operations. A key is tied to one logical request — one POST /v1/users, one POST /v1/transactions, etc. If you reuse a key from a user creation for a transaction creation, you get 409 idempotency_key_reused because the body differs.

Generating a new key per attempt instead of per intent. If your retry loop calls randomUUID() inside the loop, every retry gets a different key, and BlendFi treats each as a fresh request. You will create duplicates. Generate the key once, outside the retry loop.

Discarding keys before retrying long-running flows. Sandbox keeps idempotency records for 24 hours. If your retry happens more than 24 hours later, the key has expired and the request executes fresh. For one-shot flows (user creation, transaction execution) this is fine. For multi-day workflows, persist the response after the first successful call so you don't need to retry across the expiration boundary.

Treating in-flight conflicts as a fatal error. 409 idempotency_key_in_progress means an earlier attempt is still running. Wait a second and retry — don't error out.

When you don't need it

Read-only requests (GET, HEAD, OPTIONS) don't need an idempotency key. They have no side effects to deduplicate. The header is rejected on these methods only if you explicitly pass it with conflicting values.

DELETE is naturally idempotent — deleting an already-deleted resource returns 404, not a duplicate delete. We don't require an idempotency key on DELETE for that reason.

FAQ

What format does the key need to be? Any string between 1 and 255 characters. UUID v4 is recommended. Don't use sequential integers — collisions across systems become a real risk.

How long does BlendFi remember a key? 24 hours from first use, per organization. After that, the key is forgotten and a fresh request executes if you retry.

Can two different organizations use the same key? Yes. Idempotency records are scoped per organization, so partner A's idempotency-key: 1234 is independent from partner B's. There is no cross-tenant collision risk.

What if my retry hits a different BlendFi region? BlendFi's idempotency store is centralized — all regions consult the same record set. You won't get duplicate execution from regional failover.

Does the idempotency check verify the request body byte-for-byte? We hash method + path + body with SHA-256 and compare. Any change to any of those three triggers 409 idempotency_key_reused. Whitespace and key ordering in JSON matter — if you serialize differently on retry, you'll trip the check.

What if I want to force a re-execution after a previous request succeeded? Use a new key. There is no API to invalidate a stored response.

On this page