Safe Order Retries in B2B APIs 2026: Status-Check-Before-Retry Pattern
Idempotency keys are the technique that makes any POST request in a B2B API retry-safe. Without them, every network timeout on the client turns into a "did the order create or not?" question — and in 30% of cases the answer is "both". The FoxReload API does not support an Idempotency-Key header. This article explains the correct substitute pattern — status-check-before-retry — along with client-side deduplication strategies.
1. Why the FoxReload API has no idempotency keys
FoxReload's order endpoint at POST /api/orders does not accept an Idempotency-Key header. Each call creates a new order. This means:
- A single retry after a network blip can create two orders
- Your balance is debited twice
- Two sets of codes are delivered
The solution is entirely on the client side.
2. The status-check-before-retry pattern
Before retrying a POST /api/orders that returned a timeout or ambiguous error, check whether the order already exists:
async function safeCreateOrder(
items: OrderItem[],
apiKey: string,
): Promise<Order> {
// Step 1: Record intent in your own DB before calling the API
const clientRef = randomUUID();
await db.pendingOrders.insert({ clientRef, items, createdAt: new Date() });
try {
const res = await fetch('https://public-api.foxreload.com/api/orders', {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const order = await res.json();
await db.pendingOrders.update(clientRef, { orderId: order.id });
return order;
} catch (err) {
// Step 2: On failure, check if the order was created
const existing = await db.pendingOrders.findByRef(clientRef);
if (existing?.orderId) {
// Order was created; poll its status
return pollOrder(existing.orderId, apiKey);
}
// Step 3: Check recent FoxReload orders for a match
const recent = await fetchRecentOrders(apiKey); // GET /api/orders?limit=10
const match = findMatchingOrder(recent, items);
if (match) {
await db.pendingOrders.update(clientRef, { orderId: match.id });
return match;
}
// Step 4: Only retry if confirmed not created
throw err; // caller can retry after delay
}
}
3. Client-side deduplication storage
The key insight: store the order intent before making the API call. This gives you a paper trail to check against on failure.
| Storage | Latency | TTL | Cost / 1M records |
|---|---|---|---|
| Postgres UNIQUE | 5–8ms | ∞ | $0.10 |
| Redis SETNX | <2ms | 24h | $0.40 |
| DynamoDB ConditionExpression | 8–12ms | 24h | $1.25 |
| Memcached CAS | <1ms | 24h | $0.30 |
For most integrations, Postgres with a UNIQUE constraint on client_ref is sufficient and gives a permanent audit trail.
4. Comparing dedup approaches
| Approach | Prevents duplicates | Audit trail | Extra DB write? |
|---|---|---|---|
| Idempotency-Key (not available on FoxReload) | Yes | Server-side | No |
| Status-check-before-retry | Yes | Your DB | Yes |
| Client-side SETNX gate | Yes (within TTL) | Redis | Yes |
| Blind retry (never do this) | No | No | No |
The status-check approach adds one DB write per order attempt. At any scale below millions of orders per day, this is entirely acceptable.
5. Comparing dedup storage backends
// Postgres approach (recommended for audit)
await db.query(`
INSERT INTO order_intents (client_ref, items_hash, created_at)
VALUES ($1, $2, NOW())
ON CONFLICT (client_ref) DO NOTHING
`, [clientRef, hashItems(items)]);
6. What if you hit a race between two simultaneous requests?
If two workers try to create the same order simultaneously, a database UNIQUE constraint on client_ref will reject one of them at the DB level before either reaches FoxReload. This is the cleanest solution:
CREATE TABLE order_intents (
client_ref UUID PRIMARY KEY,
order_id UUID,
items_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
resolved_at TIMESTAMPTZ
);
Only the first inserter proceeds to call the API. The second reads the existing record and monitors the outcome.
CTA
The FoxReload API does not have idempotency keys — implement status-check-before-retry and a client_ref store in your own system. The full order API reference is available after onboarding — request access.
