v1.0

Idempotency

Safe retries with idempotency keys

The API supports idempotency for POST, PUT, and PATCH requests, allowing safe retries without duplicating side effects.

How It Works

Include an Idempotency-Key header with a unique value (e.g., a UUID):

curl -X POST \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{"amount": 500, "type": "merchantPayment"}' \
  https://api.smartmca.com/api/public/v1/deals/clx.../payments

Behavior

  • First request: Processed normally, response cached for 24 hours
  • Duplicate request (same key): Returns the cached response without re-executing
  • No key provided: Request is processed normally (no idempotency)

Key Rules

  • Keys are scoped per API key — different API keys can use the same idempotency key
  • Keys expire after 24 hours
  • Only applies to POST, PUT, and PATCH requests
  • GET and DELETE are naturally idempotent

Best Practices

  1. Generate a UUID for each unique operation
  2. Reuse the same key when retrying a failed request
  3. Never reuse keys across different operations
  4. Store the key alongside your local transaction record for debugging

Example with Retry Logic

async function createPaymentWithRetry(dealId: string, amount: number) {
  const idempotencyKey = crypto.randomUUID();

  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      const response = await fetch(
        `https://api.smartmca.com/api/public/v1/deals/${dealId}/payments`,
        {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${apiKey}`,
            'Content-Type': 'application/json',
            'Idempotency-Key': idempotencyKey,
          },
          body: JSON.stringify({ amount, type: 'merchantPayment' }),
        },
      );

      if (response.ok) return response.json();
      if (response.status === 429) {
        const retryAfter = Number(response.headers.get('Retry-After')) || 5;
        await sleep(retryAfter * 1000);
        continue;
      }
      throw new Error(`API error: ${response.status}`);
    } catch (err) {
      if (attempt === 2) throw err;
      await sleep(1000 * Math.pow(2, attempt));
    }
  }
}