Skip to main content

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 to true for immediate permanent deletion
  • reason (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

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