Idempotency Keys
Understanding and implementing idempotency keys for safe API operations
What are Idempotency Keys?
Idempotency keys are unique identifiers that ensure API operations can be safely retried without creating duplicate resources or executing the same operation multiple times. This is especially important for financial operations like token creation, token issuance, and transactions.
When you include an idempotency key with your API request, the server can detect if the same operation has already been performed and return the original result instead of creating a duplicate.
Why Use Idempotency Keys?
Network Reliability
- Network timeouts: If your request times out, you can safely retry with the same key
- Connection issues: Temporary network problems won't cause duplicate operations
- Client-side errors: Applications can retry failed requests without side effects
Financial Safety
- No duplicate transactions: Prevents accidental double-spending
- Consistent token creation: Avoids creating multiple tokens with the same symbol
- Reliable token issuance: Ensures tokens are only issued once per request
How to Generate Idempotency Keys
Idempotency keys should be:
- Unique: Each operation should have a unique key
- Random: Use UUIDs or cryptographically secure random strings
- Client-generated: Generated on the client side before making the request
- 1-64 characters: Length constraint as defined by the API
Automatic Generation (Default Behavior)
For transactions only: If you don't provide an idempotencyKey, the system will automatically generate one based on your transaction signature. This ensures backward compatibility and provides idempotency protection even for existing clients.
// This transaction will have an auto-generated idempotencyKey
const response = await fetch('/api/transactions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
from: '0x742d35Cc6634C0532925a3b8D5C',
to: '0x8ba1f109551bD432803012645Hac',
value: 100,
symbol: 'MTK',
signature: '0x...' // Key will be generated from this signature
// No idempotencyKey needed - auto-generated
})
});Manual Generation (Recommended for Production)
For better control and debugging, provide your own idempotency keys:
JavaScript Example
import { v4 as uuidv4 } from 'uuid';
// Generate a unique idempotency key
const idempotencyKey = uuidv4();
// Example: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// Use with your API request - idempotencyKey goes in the request body
const response = await fetch('/api/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: '0x742d35Cc6634C0532925a3b8D5C',
to: '0x8ba1f109551bD432803012645Hac',
value: 100,
symbol: 'MTK',
signature: '0x...',
idempotencyKey: idempotencyKey // Include in request body
})
});Python Example
import uuid
import requests
# Generate a unique idempotency key
idempotency_key = str(uuid.uuid4())
# Use with your API request - idempotencyKey goes in the request body
response = requests.post(
'https://api.blls.me/billing/transactions',
headers={
'Content-Type': 'application/json'
},
json={
'from': '0x742d35Cc6634C0532925a3b8D5C',
'to': '0x8ba1f109551bD432803012645Hac',
'value': 100,
'symbol': 'MTK',
'signature': '0x...',
'idempotencyKey': idempotency_key # Include in request body
}
)Supported Endpoints
The following API endpoints support idempotency keys as part of the request body:
Token Creation
POST /tokens
Content-Type: application/json
{
"name": "MyToken",
"symbol": "MTK",
"emission": 1000000,
"signature": "0x...",
"idempotencyKey": "create-token-f47ac10b"
}Token Issuance
POST /tokens/issue
Content-Type: application/json
{
"symbol": "MTK",
"address": "0x742d35Cc6634C0532925a3b8D5C",
"emission": 5000,
"signature": "0x...",
"idempotencyKey": "issue-token-a1b2c3d4"
}Transactions (Primary Use Case)
POST /transactions
Content-Type: application/json
{
"from": "0x742d35Cc6634C0532925a3b8D5C",
"to": "0x8ba1f109551bD432803012645Hac",
"value": 100,
"symbol": "MTK",
"signature": "0x...",
"idempotencyKey": "payment-12345678"
}Note: For transactions, if you don't provide an idempotencyKey, the system will automatically generate one based on the transaction signature. This ensures backward compatibility while providing idempotency protection.
Best Practices
1. Store Keys for Retry Logic
class APIClient {
async sendTransaction(transactionData, maxRetries = 3) {
const idempotencyKey = uuidv4();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('/api/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...transactionData,
idempotencyKey: idempotencyKey // Same key for all retries
})
});
if (response.ok) {
return await response.json();
}
if (response.status >= 400 && response.status < 500) {
// Client error, don't retry
throw new Error(`Client error: ${response.status}`);
}
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
}2. Use Meaningful Key Prefixes
// Include operation type in the key for debugging
const createTokenKey = `create-token-${uuidv4()}`;
const issueTokenKey = `issue-token-${uuidv4()}`;
const transactionKey = `payment-${uuidv4()}`;
// Or use business-specific identifiers
const invoicePaymentKey = `invoice-payment-${invoiceId}`;
const subscriptionKey = `subscription-${userId}-${Date.now()}`;3. Key Expiration
Idempotency keys typically expire after 24 hours. After expiration, using the same key will be treated as a new operation.
Error Handling
Successful Idempotent Response
If you retry with the same idempotency key, you'll receive the original response:
{
"_id": "64f1a2b3c4d5e6f7g8h9i0j1",
"name": "MyToken",
"symbol": "MTK",
"emission": 1000000,
"timestamp": "2024-10-15T18:02:52.726Z"
}Implementation Notes
Key Storage
The system stores idempotency keys with their associated request data and response for up to 24 hours. This ensures:
- Duplicate detection works correctly
- Keys eventually expire to prevent unbounded storage growth
- Retry attempts receive consistent responses
Performance Impact
Using idempotency keys has minimal performance impact:
- Keys are indexed for fast lookup
- Validation is performed before business logic
- Cached responses are returned immediately for duplicates
Common Pitfalls
❌ Don't reuse keys across different operations
// Wrong: Using same key for different operations
const key = uuidv4();
await createToken({...tokenData, idempotencyKey: key});
await sendTransaction({...txData, idempotencyKey: key}); // Don't do this!❌ Don't generate keys on retry
// Wrong: New key on each retry
for (let i = 0; i < 3; i++) {
const key = uuidv4(); // This defeats the purpose!
await sendTransaction({...data, idempotencyKey: key});
}✅ Do generate once, use for all retries
// Correct: Same key for all retry attempts
const key = uuidv4();
for (let i = 0; i < 3; i++) {
try {
return await sendTransaction({...data, idempotencyKey: key});
} catch (error) {
if (i === 2) throw error;
}
}Summary
Idempotency keys are essential for building robust applications that interact with financial APIs. They provide:
- Safety: Prevent duplicate operations
- Reliability: Enable safe retries
- Consistency: Ensure predictable behavior
Always use idempotency keys for operations that create or modify resources, especially when dealing with tokens and transactions.