Idempotency
All blockchain write operations accept an idempotency_key parameter to prevent duplicate submissions (e.g., double-minting on network retries). This is critical for financial operations where a retry could produce unintended results.
How It Works
- Include an
idempotency_key(any unique string, max 255 characters) in your request body - If the same key is sent again within the retention window, the API returns the original result without re-executing
- Keys are scoped per tenant — different tenants can use the same key independently
POST /v1/tokens/tok_abc/mint
{ "to": "0x...", "amount": "1000", "idempotency_key": "mint-user42-2024-03-06" }
→ 201 Created: { "id": "tx_abc123", "status": "pending" }
POST /v1/tokens/tok_abc/mint (same key)
{ "to": "0x...", "amount": "1000", "idempotency_key": "mint-user42-2024-03-06" }
→ 200 OK: { "id": "tx_abc123", "status": "pending" } ← same tx returned
Key Retention
Idempotency keys are retained for 24 hours from the first request. After this window, the same key can be reused for a new operation.
| Aspect | Detail |
|---|---|
| Retention window | 24 hours |
| Scope | Per tenant |
| Max length | 255 characters |
| Storage | Redis (fast lookups) |
Which Endpoints Require It
| Endpoint | Required? |
|---|---|
POST /v1/tokens/:id/mint | Yes |
POST /v1/tokens/:id/transfer | Yes |
POST /v1/tokens/:id/burn | Yes |
POST /v1/tokens/:id/approve | Yes |
POST /v1/batch | Yes |
POST /v1/vesting | Optional |
POST /v1/vaults/:id/deposit | Optional |
POST /v1/tokens (deploy) | Optional |
SDK Example
// The SDK can auto-generate idempotency keys
const tx = await urblock.tokens.mint("tok_abc123", {
to: "0x1234567890abcdef1234567890abcdef12345678",
amount: "1000000000000000000",
idempotency_key: "mint-user42-2024-03-06",
});
// Retry with the same key — safe, returns original tx
const retry = await urblock.tokens.mint("tok_abc123", {
to: "0x1234567890abcdef1234567890abcdef12345678",
amount: "1000000000000000000",
idempotency_key: "mint-user42-2024-03-06",
});
console.log(tx.id === retry.id); // true — no duplicate mint
curl Example
curl -X POST https://api.urblock.io/v1/tokens/tok_abc123/mint \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"to": "0x1234567890abcdef1234567890abcdef12345678",
"amount": "1000000000000000000",
"idempotency_key": "mint-user42-2024-03-06"
}'
Error Response
If you send the same key with different parameters, the API returns 409 Conflict:
{
"error": {
"type": "invalid_request_error",
"code": "idempotency_key_reuse",
"message": "Idempotency key already used with different parameters.",
"status": 409
}
}
Best Practices
| Practice | Example |
|---|---|
| Use deterministic keys | {action}-{userId}-{timestamp} |
| Include operation context | mint-user42-2024-03-06-reward |
| Use UUIDs for one-off ops | 550e8400-e29b-41d4-a716-446655440000 |
| Never reuse keys for different ops | Each operation gets a unique key |
| Retry safely | Same key + same params = safe retry |
Key Generation Patterns
// Pattern 1: Action + entity + date
const key = `mint-${userId}-${new Date().toISOString().slice(0, 10)}`;
// Pattern 2: Action + unique reference
const key = `transfer-invoice-${invoiceId}`;
// Pattern 3: UUID for one-off operations
const key = crypto.randomUUID();
How It's Implemented
Under the hood, Urblock stores idempotency keys in Redis with a 24-hour TTL:
1. Request arrives with idempotency_key
2. Check Redis: key exists?
→ Yes: compare params hash
→ Same: return stored response (200 OK)
→ Different: return 409 Conflict
→ No: proceed with operation, store result in Redis
3. Return response
Next Steps
- Async Operations — understand the transaction lifecycle
- Errors — error response format
- Batch Operations — batch operations also support idempotency keys
- Transactions — transaction operations that use idempotency