Guides

Idempotency

How to use idempotency keys to prevent duplicate operations.

What is Idempotency?

Idempotency ensures that an operation can be executed multiple times with the same result. In APIs, this is crucial to avoid creating duplicate resources when there are network issues or application errors.

How it Works

Beel implements idempotency via the X-Idempotency-Key header. When you include this header in write requests (POST, PUT), the API:

  1. Stores the idempotency key along with the operation result
  2. If it receives the same request with the same key, returns the stored result
  3. Does not execute the operation again

POST Requests (creation)

Always use X-Idempotency-Key when creating resources:

curl -X POST "https://app.beel.es/api/v1/facturas" \
  -H "X-API-Key: beel_sk_xxx" \
  -H "X-Idempotency-Key: factura-orden-12345" \
  -H "Content-Type: application/json" \
  -d '{
    "clienteId": "cliente-123",
    "lineas": [
      {
        "descripcion": "Consulting",
        "cantidad": 1,
        "precio": 100.00
      }
    ]
  }'

PUT Requests (update)

Also recommended for critical updates:

curl -X PUT "https://app.beel.es/api/v1/clientes/cliente-123" \
  -H "X-API-Key: beel_sk_xxx" \
  -H "X-Idempotency-Key: update-client-address-456" \
  -H "Content-Type: application/json" \
  -d '{
    "direccion": {
      "calle": "New address 123"
    }
  }'

Key Generation

The idempotency key must be unique for each logical operation you want to perform.

✅ Best Practices

// Use UUID v4
import { v4 as uuidv4 } from 'uuid';
const idempotencyKey = uuidv4(); // "550e8400-e29b-41d4-a716-446655440000"

// Combine identifiers from your system
const idempotencyKey = `invoice-order-${orderId}-${timestamp}`;

// Use external references
const idempotencyKey = `sync-shopify-order-${shopifyOrderId}`;

❌ Bad Practices

// DON'T use values that repeat
const idempotencyKey = 'create-invoice'; // ❌ Too generic
const idempotencyKey = Date.now().toString(); // ❌ Can collide
const idempotencyKey = Math.random().toString(); // ❌ Non-deterministic

Usage Scenarios

Problem: Network Error

Without idempotency:

1. Client sends request → Timeout
2. Client retries → Creates second invoice ❌
3. Result: Duplicate invoices

With idempotency:

1. Client sends request with key "abc123" → Timeout
2. Client retries with same key "abc123" → API detects duplicate
3. Result: Returns original invoice ✅

Problem: Automatic Retry

// Without idempotency - DANGER
async function createInvoice(data) {
  try {
    return await api.post('/facturas', data);
  } catch (error) {
    if (error.code === 'NETWORK_ERROR') {
      return await api.post('/facturas', data); // ❌ Creates duplicate
    }
  }
}

// With idempotency - SAFE
async function createInvoice(data, idempotencyKey) {
  const headers = { 'X-Idempotency-Key': idempotencyKey };

  try {
    return await api.post('/facturas', data, { headers });
  } catch (error) {
    if (error.code === 'NETWORK_ERROR') {
      return await api.post('/facturas', data, { headers }); // ✅ Same key
    }
  }
}

Lifetime

Idempotency keys are stored for 24 hours. After this time, the same key can be reused for a new operation.

Responses

First Request (successful)

HTTP/1.1 201 Created
X-Idempotency-Replay: false

{
  "data": {
    "id": "factura-abc123",
    "numero": "FACT-2025-001",
    ...
  }
}

Duplicate Request (with same key)

HTTP/1.1 200 OK
X-Idempotency-Replay: true

{
  "data": {
    "id": "factura-abc123",
    "numero": "FACT-2025-001",
    ...
  }
}

The X-Idempotency-Replay: true header indicates the response comes from a previous request.

Limitations

  • Keys expire after 24 hours
  • Only apply to POST and PUT requests
  • Do not apply to naturally idempotent operations (GET, DELETE)
  • Maximum length: 255 characters

Next Steps