Delete Project
Permanently delete a project and all its files. This operation is irreversible and removes the project’s GCS bucket and all stored files.
Warning: Deleting a project is permanent and irreversible. All files in the project will be deleted along with the GCS bucket.
Endpoint
DELETE /api/projects/{project_id}Authentication
Required: API key
Authorization: Bearer YOUR_API_KEYRequest
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
project_id | UUID | Yes | Unique project identifier |
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer YOUR_API_KEY | Yes |
Response
Success Response (200 OK)
{
"message": "Project deleted successfully",
"project_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Success message |
project_id | UUID | ID of deleted project |
Error Responses
| Status | Error | Description | Solution |
|---|---|---|---|
| 401 | Unauthorized | Missing or invalid authentication | Verify Bearer token or API key |
| 403 | Forbidden | Cannot delete default project OR not owner | Check if default project or verify ownership |
| 404 | Not Found | Project doesn’t exist | Check project_id is correct |
| 422 | Unprocessable Entity | Invalid UUID format | Ensure project_id is valid UUID |
| 429 | Too Many Requests | Rate limit exceeded | Wait and retry |
| 500 | Internal Server Error | Server error | Contact support if persists |
Example Error Response (403 - Default Project):
{
"detail": "Cannot delete default project"
}Example Error Response (404 Not Found):
{
"detail": "Project not found"
}Deletion Process
When you delete a project, the following happens:
- Verification: System checks you own the project and it’s not the default
- File Deletion: All files in the project are deleted from GCS
- Database Cleanup: Project record marked as deleted/inactive
- Bucket Removal: GCS bucket is deleted
- Confirmation: Success response returned
Duration: Typically 500ms to 5 seconds depending on file count.
Examples
cURL
curl -X DELETE "https://markdownapi.io/api/projects/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer YOUR_TOKEN"Python (httpx)
import httpx
import os
from uuid import UUID
async def delete_project(project_id: UUID):
"""Delete project with confirmation and error handling."""
api_key = os.getenv("MARKDOWN_API_KEY")
async with httpx.AsyncClient() as client:
try:
response = await client.delete(
f"https://markdownapi.io/api/projects/{project_id}",
headers={"Authorization": f"Bearer {api_key}"},
timeout=30.0 # Longer timeout for deletion
)
response.raise_for_status()
result = response.json()
print(f"✓ Project deleted: {result['project_id']}")
print(f" {result['message']}")
return result
except httpx.HTTPStatusError as e:
if e.response.status_code == 403:
error = e.response.json()
if "default" in error.get("detail", "").lower():
print(f"✗ Cannot delete default project")
else:
print(f"✗ Access denied")
elif e.response.status_code == 404:
print(f"✗ Project not found or already deleted")
else:
print(f"✗ HTTP error: {e.response.status_code}")
raise
# Usage
# await delete_project(UUID("a1b2c3d4-e5f6-7890-abcd-ef1234567890"))TypeScript
interface DeleteProjectResponse {
message: string;
project_id: string;
}
async function deleteProject(projectId: string): Promise<DeleteProjectResponse> {
const apiKey = process.env.MARKDOWN_API_KEY;
try {
const response = await fetch(
`https://markdownapi.io/api/projects/${projectId}`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${apiKey!}`,
},
}
);
if (!response.ok) {
if (response.status === 403) {
const error = await response.json();
if (error.detail?.toLowerCase().includes('default')) {
throw new Error('Cannot delete default project');
}
throw new Error('Access denied');
} else if (response.status === 404) {
throw new Error('Project not found or already deleted');
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result: DeleteProjectResponse = await response.json();
console.log(`✓ Project deleted: ${result.project_id}`);
console.log(` ${result.message}`);
return result;
} catch (error) {
console.error('✗ Failed to delete project:', error);
throw error;
}
}
// Usage
// await deleteProject('a1b2c3d4-e5f6-7890-abcd-ef1234567890');Common Use Cases
1. Delete with Confirmation
async def delete_project_with_confirmation(project_id: UUID):
"""Delete project with user confirmation."""
project = await get_project(project_id)
print(f"About to delete project: {project['name']}")
print(f" Files: {project['file_count']}")
print(f" Size: {project['total_size_bytes']} bytes")
confirm = input("Type project name to confirm deletion: ")
if confirm != project['name']:
print("✗ Deletion cancelled - name mismatch")
return None
return await delete_project(project_id)2. Backup Before Delete
async def backup_and_delete_project(project_id: UUID, backup_path: str):
"""Backup all files before deleting project."""
import os
# Get all files
files = await list_files(project_id)
# Download each file
for file in files:
content = await download_file(project_id, file['filename'])
file_path = os.path.join(backup_path, file['filename'])
with open(file_path, 'wb') as f:
f.write(content)
print(f"✓ Backed up {len(files)} files to {backup_path}")
# Delete project
return await delete_project(project_id)3. Bulk Delete
import asyncio
async def delete_multiple_projects(project_ids: list[UUID]):
"""Delete multiple projects (use with caution)."""
async def delete_single(project_id):
try:
return await delete_project(project_id)
except Exception as e:
print(f"Failed to delete {project_id}: {e}")
return None
results = await asyncio.gather(*[
delete_single(pid) for pid in project_ids
])
successful = [r for r in results if r is not None]
print(f"Deleted {len(successful)}/{len(project_ids)} projects")
return successful4. Safe Delete (Check First)
async def safe_delete_project(project_id: UUID):
"""Delete project with safety checks."""
# Check project exists and get details
try:
project = await get_project(project_id)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
print("✓ Project already deleted")
return None
raise
# Check if default
if project['is_default']:
raise ValueError("Cannot delete default project")
# Check file count
if project['file_count'] > 100:
print(f"⚠ Warning: Project has {project['file_count']} files")
confirm = input("Continue? (yes/no): ")
if confirm.lower() != 'yes':
print("✗ Deletion cancelled")
return None
# Proceed with deletion
return await delete_project(project_id)Protection Mechanisms
Default Project Protection
The default project cannot be deleted:
# This will fail with 403 Forbidden
try:
await delete_project(default_project_id)
except httpx.HTTPStatusError as e:
print(e.response.json())
# {"detail": "Cannot delete default project"}Ownership Verification
You can only delete your own projects:
# Attempting to delete another user's project returns 403
try:
await delete_project(other_users_project_id)
except httpx.HTTPStatusError as e:
print(e.response.json())
# {"detail": "You do not have permission to access this resource"}Performance Characteristics
Deletion Duration
| Files in Project | Typical Duration | Notes |
|---|---|---|
| 0-10 files | 500ms - 1s | Fast deletion |
| 10-100 files | 1s - 3s | Moderate deletion |
| 100-1000 files | 3s - 10s | Slower deletion |
| 1000+ files | 10s - 30s | May require patience |
Timeout Recommendations:
- Set client timeout to at least 30 seconds
- Implement progress indication for large projects
- Consider async notification for very large projects
Best Practices
1. Always Backup First
# Good practice
await backup_and_delete_project(project_id, "/backups")
# Risky
await delete_project(project_id) # No backup!2. Verify Project ID
# Good - verify before delete
project = await get_project(project_id)
print(f"Deleting: {project['name']}")
await delete_project(project_id)
# Risky - delete without verification
await delete_project(project_id)3. Use Soft Delete Pattern
Consider marking projects as inactive instead of deleting:
# Instead of deleting, mark as inactive
await update_project(project_id, name=f"[DELETED] {project['name']}")
# Then later, actually delete when confirmed4. Handle Idempotency
async def delete_project_idempotent(project_id: UUID):
"""Delete project with idempotent behavior."""
try:
return await delete_project(project_id)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
# Already deleted - treat as success
return {"message": "Project already deleted", "project_id": str(project_id)}
raiseRecovery
No Recovery: Deleted projects cannot be recovered. All files and the GCS bucket are permanently removed.
To prevent accidental deletion:
- Implement confirmation flows
- Backup files before deletion
- Use soft delete (mark inactive) before hard delete
- Restrict deletion permissions
- Audit deletion operations
See Also
- Create Project - Create new project
- Update Project - Modify project
- List Projects - View all projects
- Projects Overview - Learn about projects