Opportunities Delete API
Overviewβ
The Opportunities Delete API allows authenticated users to permanently remove opportunities from the Ring Platform. This endpoint implements secure deletion with proper authorization checks and audit logging.
Endpoint Detailsβ
- URL:
/api/opportunities/delete/[id]
- Method:
DELETE
- Authentication: Required (Bearer token or session)
- Authorization: Owner, Admin, or Entity Admin only
- Rate Limit: 30 requests per minute per user
- Audit Logging: All deletions are logged for compliance
Ring Platform Opportunity Deletionβ
Deletion Permissionsβ
Ring Platform implements strict permission controls for opportunity deletion:
Permission Levelsβ
- Owner: User who created the opportunity (full delete access)
- Entity Admin: Admin of the entity that owns the opportunity
- Platform Admin: System administrators (full access)
- Entity Member: Cannot delete opportunities (read/update only)
Confidential Opportunity Rulesβ
- Only CONFIDENTIAL users can delete confidential opportunities
- Enhanced audit logging for confidential opportunity deletions
- Additional security validations and approval workflows
- Permanent deletion with secure data wiping
Deletion Safeguardsβ
- Soft Delete Option: Mark as deleted without permanent removal
- Cascade Handling: Related applications and saves are handled appropriately
- Audit Trail: Complete deletion history maintained
- Recovery Window: Optional recovery period for accidental deletions
Request Formatβ
URL Parametersβ
id
(required): Unique identifier of the opportunity to delete
Headersβ
DELETE /api/opportunities/delete/[id]
Authorization: Bearer <token>
Query Parametersβ
permanent
(optional): Set totrue
for immediate permanent deletionreason
(optional): Reason for deletion (for audit purposes)
Response Formatβ
Success Response (200 OK)β
{
"message": "Opportunity deleted successfully",
"success": true,
"data": {
"id": "opp_abc123def456",
"title": "Senior Full-Stack Developer",
"entityId": "ent_xyz789",
"entityName": "TechCorp Solutions",
"deletedAt": "2025-01-14T16:45:00Z",
"deletedBy": "user_123",
"deletionType": "soft",
"auditId": "audit_del_789"
}
}
Error Responsesβ
Unauthorized (401)β
{
"error": "Unauthorized"
}
Forbidden (403)β
{
"error": "Access denied: Forbidden"
}
Not Found (404)β
{
"error": "Opportunity not found"
}
Permission Denied (403)β
{
"error": "You do not have permission to delete this opportunity"
}
Code Examplesβ
JavaScript/TypeScriptβ
interface OpportunityDeletionResult {
message: string;
success: boolean;
data: {
id: string;
title: string;
entityId: string;
entityName: string;
deletedAt: string;
deletedBy: string;
deletionType: 'soft' | 'permanent';
auditId: string;
};
}
async function deleteOpportunity(
opportunityId: string,
authToken: string,
options: {
permanent?: boolean;
reason?: string;
} = {}
): Promise<OpportunityDeletionResult> {
const params = new URLSearchParams();
if (options.permanent) {
params.append('permanent', 'true');
}
if (options.reason) {
params.append('reason', options.reason);
}
const url = `/api/opportunities/delete/${opportunityId}${params.toString() ? '?' + params.toString() : ''}`;
const response = await fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to delete opportunity');
}
return response.json();
}
// Usage examples
try {
// Soft delete (default)
const softDeleteResult = await deleteOpportunity('opp_abc123', userToken);
console.log('Opportunity soft deleted:', softDeleteResult.data.title);
// Permanent delete with reason
const permanentDeleteResult = await deleteOpportunity('opp_def456', userToken, {
permanent: true,
reason: 'Position filled internally'
});
console.log('Opportunity permanently deleted:', permanentDeleteResult.data.title);
} catch (error) {
console.error('Deletion failed:', error.message);
}
React Hook for Opportunity Deletionβ
import { useState } from 'react';
interface UseOpportunityDeletionResult {
deleteOpportunity: (id: string, options?: { permanent?: boolean; reason?: string }) => Promise<void>;
isDeleting: boolean;
error: string | null;
success: boolean;
}
function useOpportunityDeletion(): UseOpportunityDeletionResult {
const [isDeleting, setIsDeleting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const deleteOpportunity = async (
id: string,
options: { permanent?: boolean; reason?: string } = {}
) => {
setIsDeleting(true);
setError(null);
setSuccess(false);
try {
const token = localStorage.getItem('authToken');
if (!token) {
throw new Error('Authentication required');
}
const result = await deleteOpportunity(id, token, options);
setSuccess(true);
// Optional: trigger refresh of opportunity list
// onDelete?.(result.data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Deletion failed');
} finally {
setIsDeleting(false);
}
};
return { deleteOpportunity, isDeleting, error, success };
}
// Usage in component
function OpportunityDeleteButton({ opportunityId, onDeleted }: OpportunityDeleteButtonProps) {
const { deleteOpportunity, isDeleting, error, success } = useOpportunityDeletion();
const [showConfirm, setShowConfirm] = useState(false);
const [deleteReason, setDeleteReason] = useState('');
const handleDelete = async (permanent: boolean = false) => {
await deleteOpportunity(opportunityId, {
permanent,
reason: deleteReason || undefined
});
if (success) {
onDeleted?.(opportunityId);
setShowConfirm(false);
}
};
return (
<>
<button
onClick={() => setShowConfirm(true)}
className="delete-button"
disabled={isDeleting}
>
{isDeleting ? 'Deleting...' : 'Delete Opportunity'}
</button>
{showConfirm && (
<div className="delete-confirmation-modal">
<div className="modal-content">
<h3>Delete Opportunity</h3>
<p>Are you sure you want to delete this opportunity?</p>
<div className="form-group">
<label>Reason for deletion (optional):</label>
<textarea
value={deleteReason}
onChange={(e) => setDeleteReason(e.target.value)}
placeholder="e.g., Position filled, Requirements changed..."
/>
</div>
{error && <div className="error-message">{error}</div>}
<div className="modal-actions">
<button
onClick={() => handleDelete(false)}
className="soft-delete-button"
disabled={isDeleting}
>
Soft Delete
</button>
<button
onClick={() => handleDelete(true)}
className="permanent-delete-button"
disabled={isDeleting}
>
Permanent Delete
</button>
<button
onClick={() => setShowConfirm(false)}
className="cancel-button"
disabled={isDeleting}
>
Cancel
</button>
</div>
</div>
</div>
)}
</>
);
}
cURL Exampleβ
# Soft delete opportunity
curl -X DELETE "https://ring.ck.ua/api/opportunities/delete/opp_abc123def456" \
-H "Authorization: Bearer YOUR_AUTH_TOKEN"
# Permanent delete with reason
curl -X DELETE "https://ring.ck.ua/api/opportunities/delete/opp_def456ghi789?permanent=true&reason=Position%20filled" \
-H "Authorization: Bearer YOUR_AUTH_TOKEN"
# Expected response
# {
# "message": "Opportunity deleted successfully",
# "success": true,
# "data": {
# "id": "opp_abc123def456",
# "title": "Senior Full-Stack Developer",
# "deletedAt": "2025-01-14T16:45:00Z",
# "deletionType": "soft"
# }
# }
Python Exampleβ
import requests
from typing import Dict, Any, Optional
class OpportunityDeleter:
def __init__(self, base_url: str, auth_token: str):
self.base_url = base_url.rstrip('/')
self.auth_token = auth_token
self.headers = {
'Authorization': f'Bearer {auth_token}'
}
def delete_opportunity(
self,
opportunity_id: str,
permanent: bool = False,
reason: Optional[str] = None
) -> Dict[str, Any]:
"""Delete an opportunity (soft or permanent)"""
url = f"{self.base_url}/api/opportunities/delete/{opportunity_id}"
params = {}
if permanent:
params['permanent'] = 'true'
if reason:
params['reason'] = reason
response = requests.delete(url, headers=self.headers, params=params)
if response.status_code == 200:
return response.json()
elif response.status_code == 401:
raise Exception("Unauthorized - check your authentication token")
elif response.status_code == 403:
raise Exception("Forbidden - insufficient permissions to delete this opportunity")
elif response.status_code == 404:
raise Exception("Opportunity not found")
else:
error_data = response.json()
raise Exception(f"Deletion failed: {error_data.get('error', 'Unknown error')}")
def soft_delete(self, opportunity_id: str, reason: Optional[str] = None) -> Dict[str, Any]:
"""Convenience method for soft deletion"""
return self.delete_opportunity(opportunity_id, permanent=False, reason=reason)
def permanent_delete(self, opportunity_id: str, reason: Optional[str] = None) -> Dict[str, Any]:
"""Convenience method for permanent deletion"""
return self.delete_opportunity(opportunity_id, permanent=True, reason=reason)
# Usage examples
deleter = OpportunityDeleter("https://ring.ck.ua", "your-auth-token")
try:
# Soft delete with reason
soft_result = deleter.soft_delete("opp_abc123", "Position requirements changed")
print(f"Soft deleted: {soft_result['data']['title']}")
print(f"Deletion type: {soft_result['data']['deletionType']}")
# Permanent delete
permanent_result = deleter.permanent_delete("opp_def456", "Position filled internally")
print(f"Permanently deleted: {permanent_result['data']['title']}")
# Bulk deletion with error handling
opportunity_ids = ["opp_123", "opp_456", "opp_789"]
deletion_results = []
for opp_id in opportunity_ids:
try:
result = deleter.soft_delete(opp_id, "Bulk cleanup")
deletion_results.append({
'id': opp_id,
'status': 'success',
'title': result['data']['title']
})
except Exception as e:
deletion_results.append({
'id': opp_id,
'status': 'error',
'error': str(e)
})
# Summary
successful = [r for r in deletion_results if r['status'] == 'success']
failed = [r for r in deletion_results if r['status'] == 'error']
print(f"Bulk deletion complete: {len(successful)} successful, {len(failed)} failed")
except Exception as e:
print(f"Error deleting opportunity: {e}")
Deletion Typesβ
Soft Deletion (Default)β
- Behavior: Marks opportunity as deleted without removing data
- Recovery: Can be restored by administrators
- Visibility: Hidden from public listings but preserved in database
- Audit: Full audit trail maintained
- Related Data: Applications and saves are preserved
Permanent Deletionβ
- Behavior: Completely removes opportunity and related data
- Recovery: Cannot be restored (irreversible)
- Visibility: Completely removed from all systems
- Audit: Deletion event logged but data is wiped
- Related Data: Applications and saves are also removed
Security Considerationsβ
Permission Validationβ
- Ownership Check: Verify user owns the opportunity or has entity admin rights
- Confidential Access: Additional validation for confidential opportunities
- Entity Membership: Ensure user belongs to the opportunity's entity
- Admin Override: Platform administrators can delete any opportunity
Audit and Complianceβ
- Deletion Logging: All deletions are logged with user, timestamp, and reason
- Data Retention: Comply with data retention policies and regulations
- Recovery Procedures: Documented procedures for data recovery requests
- Security Monitoring: Monitor for unusual deletion patterns
Data Protectionβ
- Secure Wiping: Permanent deletions use secure data wiping techniques
- Backup Handling: Ensure deleted data is removed from backups
- Related Data: Handle deletion of related files, images, and attachments
- Privacy Compliance: Ensure deletion meets privacy regulation requirements
Related Endpointsβ
- Create Opportunity - Create new opportunities
- Update Opportunity - Modify existing opportunities
- Get Opportunity - Retrieve opportunity details
- List Opportunities - Browse and search opportunities
Changelogβ
- v1.0.0 - Initial implementation with basic deletion functionality
- v1.1.0 - Added soft delete option and audit logging
- v1.2.0 - Enhanced permission system and confidential opportunity support
- v1.3.0 - Improved security measures and compliance features