Delete Product

The Delete Product endpoint permanently removes a product from your catalog. This operation is irreversible and will delete all product data including images, variations, and associated metadata. The product will no longer be available for purchase or inclusion in payment links and invoices.

Endpoint DetailsCopied!

  • Method: DELETE

  • URL: /api/v0/products/{id}

  • Authentication: Required (API Key & Secret)

  • Rate Limiting: 100 requests per minute

  • Idempotency: Supported (recommended for delete operations)

Path ParametersCopied!

Parameter

Type

Required

Description

Example

id

string (UUID)

Yes

Unique product identifier

550e8400-e29b-41d4-a716-446655440000

Request ExamplesCopied!

Basic Product Deletion

curl -X DELETE "https://api.devdraft.com/api/v0/products/550e8400-e29b-41d4-a716-446655440000" \
  -H "x-client-key: YOUR_CLIENT_KEY" \
  -H "x-client-secret: YOUR_CLIENT_SECRET" \
  -H "x-idempotency-key: $(uuidgen)"

JavaScript/Node.js Example

const deleteProduct = async (productId) => {
  try {
    const response = await fetch(`https://api.devdraft.com/api/v0/products/${productId}`, {
      method: 'DELETE',
      headers: {
        'x-client-key': 'YOUR_CLIENT_KEY',
        'x-client-secret': 'YOUR_CLIENT_SECRET',
        'x-idempotency-key': generateUUID()
      }
    });

    if (!response.ok) {
      if (response.status === 404) {
        throw new Error('Product not found');
      }
      if (response.status === 409) {
        throw new Error('Product cannot be deleted due to existing references');
      }
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const deletedProduct = await response.json();
    return deletedProduct;
  } catch (error) {
    console.error('Error deleting product:', error);
    throw error;
  }
};

// Usage with confirmation
const handleProductDeletion = async (productId, productName) => {
  // Always confirm before deletion
  const confirmDelete = confirm(
    `Are you sure you want to delete "${productName}"? This action cannot be undone.`
  );
  
  if (!confirmDelete) {
    return null;
  }

  try {
    const deletedProduct = await deleteProduct(productId);
    console.log(`Successfully deleted product: ${deletedProduct.name}`);
    return deletedProduct;
  } catch (error) {
    console.error('Failed to delete product:', error.message);
    throw error;
  }
};

// Example usage
try {
  const result = await handleProductDeletion(
    '550e8400-e29b-41d4-a716-446655440000',
    'Premium Software License'
  );
  
  if (result) {
    // Update UI to remove product from list
    removeProductFromUI(result.id);
  }
} catch (error) {
  alert('Failed to delete product: ' + error.message);
}

Python Example

import requests
import uuid

def delete_product(product_id, client_key, client_secret, confirm=True):
    """Delete a product with optional confirmation"""
    if confirm:
        response = input(f"Are you sure you want to delete product {product_id}? (yes/no): ")
        if response.lower() != 'yes':
            print("Deletion cancelled")
            return None
    
    url = f"https://api.devdraft.com/api/v0/products/{product_id}"
    
    headers = {
        'x-client-key': client_key,
        'x-client-secret': client_secret,
        'x-idempotency-key': str(uuid.uuid4())
    }
    
    try:
        response = requests.delete(url, headers=headers)
        
        if response.status_code == 404:
            raise ValueError("Product not found")
        elif response.status_code == 409:
            raise ValueError("Product cannot be deleted due to existing references")
        
        response.raise_for_status()
        return response.json()
        
    except requests.exceptions.RequestException as e:
        raise Exception(f"Failed to delete product: {e}")

# Usage example
try:
    deleted_product = delete_product(
        "550e8400-e29b-41d4-a716-446655440000",
        "YOUR_CLIENT_KEY",
        "YOUR_CLIENT_SECRET"
    )
    
    if deleted_product:
        print(f"Successfully deleted: {deleted_product['name']}")
except Exception as e:
    print(f"Error: {e}")

React Component Example

import React, { useState } from 'react';

const ProductDeletionModal = ({ product, onDelete, onCancel }) => {
  const [isDeleting, setIsDeleting] = useState(false);
  const [confirmText, setConfirmText] = useState('');

  const handleDelete = async () => {
    // Require typing product name for confirmation
    if (confirmText !== product.name) {
      alert('Please type the exact product name to confirm deletion');
      return;
    }

    setIsDeleting(true);
    
    try {
      await deleteProduct(product.id);
      onDelete(product);
    } catch (error) {
      alert('Failed to delete product: ' + error.message);
    } finally {
      setIsDeleting(false);
    }
  };

  return (
    <div className="modal-overlay">
      <div className="modal-content">
        <h2>Delete Product</h2>
        <div className="warning-box">
          <p><strong>⚠️ Warning:</strong> This action cannot be undone.</p>
          <p>Deleting this product will:</p>
          <ul>
            <li>Permanently remove all product data</li>
            <li>Delete associated images from storage</li>
            <li>Remove the product from all payment links</li>
            <li>Prevent future sales of this product</li>
          </ul>
        </div>
        
        <div className="product-info">
          <h3>Product to Delete:</h3>
          <p><strong>Name:</strong> {product.name}</p>
          <p><strong>Price:</strong> ${product.price}</p>
          <p><strong>Status:</strong> {product.status}</p>
        </div>
        
        <div className="confirmation-input">
          <label>
            Type "<strong>{product.name}</strong>" to confirm deletion:
            <input
              type="text"
              value={confirmText}
              onChange={(e) => setConfirmText(e.target.value)}
              placeholder="Type product name here"
              disabled={isDeleting}
            />
          </label>
        </div>
        
        <div className="modal-actions">
          <button 
            onClick={onCancel}
            disabled={isDeleting}
            className="btn-cancel"
          >
            Cancel
          </button>
          <button 
            onClick={handleDelete}
            disabled={isDeleting || confirmText !== product.name}
            className="btn-delete"
          >
            {isDeleting ? 'Deleting...' : 'Delete Product'}
          </button>
        </div>
      </div>
    </div>
  );
};

Response FormatCopied!

Success Response (200 OK)

The response returns the deleted product's final state before removal:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Premium Software License",
  "description": "Annual license for our premium software suite with advanced features, priority support, and regular updates.",
  "price": 299.99,
  "currency": "USD",
  "productType": "PRODUCT",
  "status": "ACTIVE",
  "stockCount": null,
  "quantity": null,
  "weight": null,
  "unit": null,
  "images": [
    "https://devdraft-images.s3.amazonaws.com/products/software-license-main.jpg",
    "https://devdraft-images.s3.amazonaws.com/products/software-license-features.jpg"
  ],
  "variations": null,
  "paymentLink": "https://pay.devdraft.com/p/premium-license",
  "walletId": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
  "dateAdded": "2024-01-15T10:30:00.000Z",
  "dateUpdated": "2024-01-15T10:30:00.000Z",
  "wallet": {
    "id": "abcd1234-5678-90ef-ghij-klmnopqrstuv",
    "address": "0x742d35Cc6635C0532925a3b8d",
    "blockchain": "ETHEREUM",
    "type": "APP"
  },
  "transactions": [
    {
      "id": "txn_123456789",
      "amount": 299.99,
      "currency": "USD",
      "status": "PAYMENT_PROCESSED",
      "dateCreated": "2024-01-15T11:00:00.000Z"
    }
  ]
}

Error ResponsesCopied!

Product Not Found (404 Not Found)

{
  "statusCode": 404,
  "message": "Product not found",
  "error": "Not Found"
}

Invalid Product ID (400 Bad Request)

{
  "statusCode": 400,
  "message": "Invalid product ID format. Must be a valid UUID.",
  "error": "Bad Request"
}

Foreign Key Constraint (409 Conflict)

{
  "statusCode": 409,
  "message": "Cannot delete product. Product is referenced by existing invoices, payment links, or transactions.",
  "error": "Conflict",
  "details": {
    "references": [
      "5 active payment links",
      "12 pending invoices",
      "48 completed transactions"
    ]
  }
}

Authentication Error (401 Unauthorized)

{
  "statusCode": 401,
  "message": "Invalid or missing API credentials",
  "error": "Unauthorized"
}

Rate Limit Error (429 Too Many Requests)

{
  "statusCode": 429,
  "message": "Rate limit exceeded. Maximum 100 requests per minute.",
  "error": "Too Many Requests",
  "retryAfter": 60
}

Insufficient Permissions (403 Forbidden)

{
  "statusCode": 403,
  "message": "Insufficient permissions to delete products",
  "error": "Forbidden"
}

Business Logic & ConstraintsCopied!

Deletion Rules

  1. Product Ownership: Can only delete products belonging to your application

  2. Foreign Key Constraints: Products with existing references cannot be deleted

  3. Transaction History: Products with completed transactions are preserved in transaction records

  4. Image Cleanup: Product images are automatically removed from cloud storage

  5. Payment Link Updates: Active payment links are automatically updated to remove the product

Referenced Data Handling

Reference Type

Behavior

Impact

Active Payment Links

Product removed from payment links

Payment links continue to work with remaining products

Pending Invoices

Deletion blocked

Must resolve or cancel invoices first

Completed Transactions

Transaction history preserved

Product reference maintained for historical records

Draft Invoices

Product removed automatically

Invoices updated to exclude deleted product

Product Images

Automatically deleted

Images removed from cloud storage

Audit Trail

  • All product deletions are logged in the audit trail

  • Includes user information, timestamp, and product details

  • Provides accountability and compliance tracking

  • Cannot be modified or deleted

Advanced Use CasesCopied!

Safe Deletion with Validation

// Comprehensive deletion validation
const safeDeleteProduct = async (productId) => {
  try {
    // 1. Fetch product details first
    const product = await fetchProduct(productId);
    
    // 2. Check for active references
    const references = await checkProductReferences(productId);
    
    if (references.hasActiveReferences) {
      throw new Error(
        `Cannot delete product. Found active references: ${references.details.join(', ')}`
      );
    }
    
    // 3. Confirm deletion with user
    const confirmed = await confirmDeletion(product);
    if (!confirmed) {
      return null;
    }
    
    // 4. Create backup before deletion (optional)
    await createProductBackup(product);
    
    // 5. Perform deletion
    const deletedProduct = await deleteProduct(productId);
    
    // 6. Clean up related data
    await cleanupProductReferences(productId);
    
    return deletedProduct;
  } catch (error) {
    console.error('Safe deletion failed:', error);
    throw error;
  }
};

// Check for product references
const checkProductReferences = async (productId) => {
  const checks = await Promise.all([
    checkPaymentLinks(productId),
    checkPendingInvoices(productId),
    checkActiveTransactions(productId)
  ]);
  
  const activeReferences = checks.filter(check => check.hasReferences);
  
  return {
    hasActiveReferences: activeReferences.length > 0,
    details: activeReferences.map(ref => ref.description)
  };
};

Bulk Product Deletion

// Delete multiple products with safety checks
const bulkDeleteProducts = async (productIds, options = {}) => {
  const { confirmAll = true, skipOnError = false } = options;
  const results = [];
  
  // Validate all products first
  const validationResults = await Promise.all(
    productIds.map(async (id) => {
      try {
        const product = await fetchProduct(id);
        const references = await checkProductReferences(id);
        return { id, product, references, valid: !references.hasActiveReferences };
      } catch (error) {
        return { id, error: error.message, valid: false };
      }
    })
  );
  
  const validProducts = validationResults.filter(r => r.valid);
  const invalidProducts = validationResults.filter(r => !r.valid);
  
  if (invalidProducts.length > 0) {
    console.warn(`${invalidProducts.length} products cannot be deleted due to constraints`);
    
    if (!skipOnError) {
      throw new Error(
        `Cannot delete products with active references: ${invalidProducts.map(p => p.id).join(', ')}`
      );
    }
  }
  
  // Confirm bulk deletion
  if (confirmAll && validProducts.length > 0) {
    const confirmed = confirm(
      `Delete ${validProducts.length} products? This action cannot be undone.`
    );
    if (!confirmed) {
      return { deleted: [], skipped: productIds.length, errors: [] };
    }
  }
  
  // Delete valid products in batches
  const batchSize = 10;
  for (let i = 0; i < validProducts.length; i += batchSize) {
    const batch = validProducts.slice(i, i + batchSize);
    
    const batchResults = await Promise.all(
      batch.map(async ({ id, product }) => {
        try {
          const deleted = await deleteProduct(id);
          return { success: true, id, product: deleted };
        } catch (error) {
          return { success: false, id, error: error.message };
        }
      })
    );
    
    results.push(...batchResults);
    
    // Rate limiting delay
    if (i + batchSize < validProducts.length) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  
  const successful = results.filter(r => r.success);
  const failed = results.filter(r => !r.success);
  
  return {
    deleted: successful.map(r => r.product),
    skipped: invalidProducts.length,
    errors: failed.map(r => ({ id: r.id, error: r.error })),
    summary: `Deleted ${successful.length}, skipped ${invalidProducts.length}, failed ${failed.length}`
  };
};

Product Archival Alternative

// Archive instead of delete for better data preservation
const archiveProduct = async (productId) => {
  try {
    // Set product to inactive status
    const archivedProduct = await updateProduct(productId, {
      status: 'INACTIVE',
      // Add archive metadata
      description: `[ARCHIVED] ${product.description}`,
      // Move to archive category if using categories
      categoryId: 'archived-products'
    });
    
    // Log archival action
    await logAuditEvent({
      action: 'PRODUCT_ARCHIVED',
      entityId: productId,
      timestamp: new Date().toISOString(),
      metadata: { originalStatus: 'ACTIVE' }
    });
    
    return archivedProduct;
  } catch (error) {
    console.error('Archival failed:', error);
    throw error;
  }
};

// Restore archived product
const restoreProduct = async (productId) => {
  const product = await fetchProduct(productId);
  
  return await updateProduct(productId, {
    status: 'ACTIVE',
    description: product.description.replace('[ARCHIVED] ', ''),
    categoryId: null // Remove from archive category
  });
};

Deletion with Data Export

// Export product data before deletion
const deleteWithExport = async (productId, exportFormat = 'json') => {
  try {
    // 1. Fetch complete product data
    const product = await fetchProduct(productId);
    
    // 2. Gather related data
    const relatedData = await gatherProductData(productId);
    
    // 3. Create export package
    const exportData = {
      product,
      transactions: relatedData.transactions,
      analytics: relatedData.analytics,
      images: relatedData.images,
      exportedAt: new Date().toISOString(),
      reason: 'Product deletion backup'
    };
    
    // 4. Save export
    const exportFile = await saveExport(exportData, exportFormat);
    console.log(`Product data exported to: ${exportFile}`);
    
    // 5. Perform deletion
    const deletedProduct = await deleteProduct(productId);
    
    return {
      deleted: deletedProduct,
      export: exportFile,
      exportData: exportData
    };
  } catch (error) {
    console.error('Delete with export failed:', error);
    throw error;
  }
};

const gatherProductData = async (productId) => {
  const [transactions, analytics] = await Promise.all([
    getProductTransactions(productId),
    getProductAnalytics(productId)
  ]);
  
  return { transactions, analytics };
};

Integration PatternsCopied!

Error Handling & Recovery

// Robust deletion with error recovery
const deleteProductWithRecovery = async (productId, options = {}) => {
  const { backup = true, retryOnFailure = true } = options;
  let backupData = null;
  
  try {
    // Create backup if requested
    if (backup) {
      const product = await fetchProduct(productId);
      backupData = await createProductBackup(product);
    }
    
    // Attempt deletion
    const result = await deleteProduct(productId);
    
    // Cleanup backup on success
    if (backupData && backupData.cleanupOnSuccess) {
      await cleanupBackup(backupData.id);
    }
    
    return result;
  } catch (error) {
    // Recovery logic
    if (error.message.includes('Foreign key constraint') && retryOnFailure) {
      console.warn('Deletion failed due to constraints, attempting cleanup...');
      
      try {
        await cleanupProductReferences(productId);
        return await deleteProduct(productId); // Retry
      } catch (retryError) {
        console.error('Retry failed:', retryError);
        throw retryError;
      }
    }
    
    // Restore from backup if critical failure
    if (backupData && error.message.includes('data corruption')) {
      console.warn('Attempting to restore from backup...');
      await restoreFromBackup(backupData);
    }
    
    throw error;
  }
};

Audit and Compliance

// Comprehensive audit logging for deletions
const auditedDelete = async (productId, userId, reason = '') => {
  const startTime = Date.now();
  
  try {
    // Pre-deletion audit
    const product = await fetchProduct(productId);
    await logAuditEvent({
      action: 'PRODUCT_DELETE_INITIATED',
      entityId: productId,
      userId,
      metadata: {
        productName: product.name,
        productValue: product.price,
        reason,
        timestamp: new Date().toISOString()
      }
    });
    
    // Perform deletion
    const deletedProduct = await deleteProduct(productId);
    
    // Post-deletion audit
    await logAuditEvent({
      action: 'PRODUCT_DELETE_COMPLETED',
      entityId: productId,
      userId,
      metadata: {
        productName: deletedProduct.name,
        duration: Date.now() - startTime,
        success: true,
        timestamp: new Date().toISOString()
      }
    });
    
    return deletedProduct;
  } catch (error) {
    // Failure audit
    await logAuditEvent({
      action: 'PRODUCT_DELETE_FAILED',
      entityId: productId,
      userId,
      metadata: {
        error: error.message,
        duration: Date.now() - startTime,
        timestamp: new Date().toISOString()
      }
    });
    
    throw error;
  }
};

Security ConsiderationsCopied!

Access Control

// Verify permissions before deletion
const authorizedDelete = async (productId, userToken) => {
  try {
    // Verify user has delete permissions
    const permissions = await verifyUserPermissions(userToken);
    if (!permissions.includes('delete:products')) {
      throw new Error('Insufficient permissions to delete products');
    }
    
    // Verify product ownership
    const product = await fetchProduct(productId);
    const userApps = await getUserApplications(userToken);
    
    if (!userApps.includes(product.app_id)) {
      throw new Error('Cannot delete product from different application');
    }
    
    return await deleteProduct(productId);
  } catch (error) {
    console.error('Authorization failed:', error);
    throw error;
  }
};

Data Protection

// Secure deletion with data wiping
const secureDelete = async (productId) => {
  try {
    // 1. Delete product record
    const deletedProduct = await deleteProduct(productId);
    
    // 2. Securely wipe image files
    if (deletedProduct.images && deletedProduct.images.length > 0) {
      await Promise.all(
        deletedProduct.images.map(imageUrl => secureDeleteImage(imageUrl))
      );
    }
    
    // 3. Clear any cached data
    await clearProductCache(productId);
    
    // 4. Notify related services
    await notifyDeletionServices(productId);
    
    return deletedProduct;
  } catch (error) {
    console.error('Secure deletion failed:', error);
    throw error;
  }
};

Best PracticesCopied!

Confirmation & Validation

  • Always confirm destructive operations with users

  • Validate permissions before allowing deletion

  • Check for dependencies that would prevent deletion

  • Provide clear warnings about irreversible consequences

Data Management

  • Create backups before deletion for recovery purposes

  • Export important data for compliance and historical records

  • Clean up related resources (images, cache entries)

  • Update dependent systems that reference the product

User Experience

  • Show deletion impact (number of affected items)

  • Provide alternatives like archiving for reversible removal

  • Give clear feedback about deletion success or failure

  • Offer bulk operations for efficiency when managing multiple products

Error Recovery

  • Implement retry logic for transient failures

  • Provide rollback capabilities where possible

  • Log all deletion attempts for audit and debugging

  • Handle constraint violations gracefully with helpful messages

Next StepsCopied!

After deleting a product, consider:

  1. Update Related Systems: Remove product references from external systems

  2. Clean Analytics Data: Archive or remove product performance data

  3. Notify Stakeholders: Inform teams about product removal

  4. Review Dependencies: Check for any missed references that need cleanup

  5. Monitor Impact: Ensure deletion doesn't break existing workflows

For more information, see: