Delete Scheduled Post
Delete a scheduled post from Twitter, LinkedIn, Instagram, Threads, and any other platforms where it was scheduled—all in a single API call.
Overview
When you create a post targeting multiple platforms, PostPost creates a post group containing individual platform-specific posts. The delete endpoint removes the entire group atomically, ensuring consistent cleanup across all platforms.
API Reference
Endpoint:
DELETE https://api.postpost.dev/api/v1/delete-post/:postGroupIdHeaders:
x-api-key: YOUR_API_KEYPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
postGroupId | string | Yes | The post group ID returned when the post was created |
Success Response (200):
{
"success": true
}What Gets Deleted
A single delete request removes:
- The post group record — Parent container for all platform posts
- All platform-specific posts — Twitter, LinkedIn, Instagram, Threads, etc.
- All media file records — Database references to images/videos
- All media files from storage — Actual files in S3
Post Group (postGroupId: 507f1f77bcf86cd799439011)
├── Twitter post
├── LinkedIn post
└── Instagram post
DELETE /api/v1/delete-post/507f1f77bcf86cd799439011
→ All three platform posts deleted in one requestComplete JavaScript Implementation
const PUBLORA_API_KEY = process.env.PUBLORA_API_KEY;
const BASE_URL = 'https://api.postpost.dev/api/v1';
/**
* Custom error class for PostPost API errors
*/
class PostPostError extends Error {
constructor(message, statusCode, response) {
super(message);
this.name = 'PostPostError';
this.statusCode = statusCode;
this.response = response;
}
}
/**
* Delete a scheduled post across all platforms
* @param {string} postGroupId - The post group ID to delete
* @returns {Promise<{success: boolean}>} Deletion result
* @throws {PostPostError} If deletion fails
*/
async function deleteScheduledPost(postGroupId) {
// Validate input
if (!postGroupId) {
throw new Error('postGroupId is required');
}
if (typeof postGroupId !== 'string') {
throw new Error('postGroupId must be a string');
}
// Make API request
const response = await fetch(`${BASE_URL}/delete-post/${postGroupId}`, {
method: 'DELETE',
headers: {
'x-api-key': PUBLORA_API_KEY
}
});
// Handle non-JSON responses (network errors, etc.)
let data;
try {
data = await response.json();
} catch (e) {
throw new PostPostError(
`Unexpected response: ${response.statusText}`,
response.status,
null
);
}
// Handle API errors
if (!response.ok) {
let errorMessage = data.error || `HTTP ${response.status}`;
switch (response.status) {
case 401:
errorMessage = 'Invalid API key. Check your x-api-key header.';
break;
case 404:
errorMessage = 'Post not found. It may have been deleted or belongs to another user.';
break;
case 500:
errorMessage = 'Server error during deletion. The operation was rolled back.';
break;
}
throw new PostPostError(errorMessage, response.status, data);
}
return data;
}
/**
* Delete multiple posts with error tracking
* @param {string[]} postGroupIds - Array of post group IDs to delete
* @returns {Promise<{succeeded: string[], failed: Array<{postGroupId: string, error: string}>}>}
*/
async function deleteMultiplePosts(postGroupIds) {
if (!Array.isArray(postGroupIds) || postGroupIds.length === 0) {
throw new Error('postGroupIds must be a non-empty array');
}
const results = {
succeeded: [],
failed: []
};
for (const postGroupId of postGroupIds) {
try {
await deleteScheduledPost(postGroupId);
results.succeeded.push(postGroupId);
} catch (error) {
results.failed.push({
postGroupId,
error: error.message,
statusCode: error.statusCode
});
}
}
return results;
}
/**
* Delete all scheduled posts for a specific platform
* @param {string} platform - Platform name (twitter, linkedin, instagram, threads)
* @returns {Promise<number>} Number of posts deleted
*/
async function deleteAllScheduledForPlatform(platform) {
const validPlatforms = ['twitter', 'linkedin', 'instagram', 'threads', 'facebook'];
if (!validPlatforms.includes(platform)) {
throw new Error(`Invalid platform. Must be one of: ${validPlatforms.join(', ')}`);
}
let deletedCount = 0;
let page = 1;
while (true) {
// Fetch scheduled posts for this platform
const response = await fetch(
`${BASE_URL}/list-posts?status=scheduled&platform=${platform}&page=${page}&limit=100`,
{ headers: { 'x-api-key': PUBLORA_API_KEY } }
);
const data = await response.json();
if (!data.posts || data.posts.length === 0) {
break;
}
// Delete each post
for (const post of data.posts) {
try {
await deleteScheduledPost(post.postGroupId);
deletedCount++;
console.log(`Deleted: ${post.postGroupId}`);
} catch (error) {
console.error(`Failed to delete ${post.postGroupId}: ${error.message}`);
}
}
// Check for more pages
if (!data.pagination?.hasNextPage) {
break;
}
page++;
}
return deletedCount;
}
// ============ USAGE EXAMPLES ============
// Example 1: Delete a single post
async function deleteSinglePost() {
try {
const result = await deleteScheduledPost('507f1f77bcf86cd799439011');
console.log('Post deleted successfully');
return result;
} catch (error) {
if (error.statusCode === 404) {
console.log('Post already deleted or not found');
} else {
console.error('Failed to delete:', error.message);
}
throw error;
}
}
// Example 2: Delete multiple posts with summary
async function deleteBatch() {
const postIds = [
'507f1f77bcf86cd799439011',
'507f1f77bcf86cd799439012',
'507f1f77bcf86cd799439013'
];
const results = await deleteMultiplePosts(postIds);
console.log(`Succeeded: ${results.succeeded.length}`);
console.log(`Failed: ${results.failed.length}`);
if (results.failed.length > 0) {
console.log('Failed deletions:');
results.failed.forEach(f => console.log(` ${f.postGroupId}: ${f.error}`));
}
return results;
}
// Example 3: Delete with confirmation (fetch details first)
async function deleteWithConfirmation(postGroupId) {
// First, get post details
const getResponse = await fetch(`${BASE_URL}/get-post/${postGroupId}`, {
headers: { 'x-api-key': PUBLORA_API_KEY }
});
if (!getResponse.ok) {
throw new Error('Post not found');
}
const postData = await getResponse.json();
const platforms = postData.posts.map(p => p.platform).join(', ');
const content = postData.posts[0]?.content?.slice(0, 50) || '';
console.log('About to delete:');
console.log(` Content: ${content}...`);
console.log(` Platforms: ${platforms}`);
console.log(` Status: ${postData.posts[0]?.status}`);
// Perform deletion
return await deleteScheduledPost(postGroupId);
}
// Run example
const result = await deleteSinglePost();Complete Python Implementation
import os
import requests
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass
PUBLORA_API_KEY = os.environ.get('PUBLORA_API_KEY')
BASE_URL = 'https://api.postpost.dev/api/v1'
class PostPostError(Exception):
"""Custom exception for PostPost API errors."""
def __init__(self, message: str, status_code: int = None, response: dict = None):
super().__init__(message)
self.status_code = status_code
self.response = response
@dataclass
class DeleteResult:
"""Result of a delete operation."""
succeeded: List[str]
failed: List[Dict[str, str]]
def delete_scheduled_post(post_group_id: str) -> Dict:
"""
Delete a scheduled post across all platforms.
Args:
post_group_id: The post group ID to delete
Returns:
dict: Deletion result with success status
Raises:
PostPostError: If the API request fails
ValueError: If inputs are invalid
"""
# Validate input
if not post_group_id:
raise ValueError('post_group_id is required')
if not isinstance(post_group_id, str):
raise ValueError('post_group_id must be a string')
# Make API request
response = requests.delete(
f'{BASE_URL}/delete-post/{post_group_id}',
headers={'x-api-key': PUBLORA_API_KEY}
)
# Handle non-JSON responses
try:
data = response.json()
except ValueError:
raise PostPostError(
f'Unexpected response: {response.text}',
response.status_code,
None
)
# Handle API errors
if not response.ok:
error_message = data.get('error', f'HTTP {response.status_code}')
if response.status_code == 401:
error_message = 'Invalid API key. Check your x-api-key header.'
elif response.status_code == 404:
error_message = 'Post not found. It may have been deleted or belongs to another user.'
elif response.status_code == 500:
error_message = 'Server error during deletion. The operation was rolled back.'
raise PostPostError(error_message, response.status_code, data)
return data
def delete_multiple_posts(post_group_ids: List[str]) -> DeleteResult:
"""
Delete multiple posts with error tracking.
Args:
post_group_ids: List of post group IDs to delete
Returns:
DeleteResult: Contains lists of succeeded and failed deletions
"""
if not post_group_ids:
raise ValueError('post_group_ids must be a non-empty list')
succeeded = []
failed = []
for post_group_id in post_group_ids:
try:
delete_scheduled_post(post_group_id)
succeeded.append(post_group_id)
except (PostPostError, ValueError) as e:
failed.append({
'postGroupId': post_group_id,
'error': str(e),
'statusCode': getattr(e, 'status_code', None)
})
return DeleteResult(succeeded=succeeded, failed=failed)
def delete_all_scheduled_for_platform(platform: str) -> int:
"""
Delete all scheduled posts that include a specific platform.
Args:
platform: Platform name (twitter, linkedin, instagram, threads)
Returns:
int: Number of posts deleted
"""
valid_platforms = ['twitter', 'linkedin', 'instagram', 'threads', 'facebook']
if platform not in valid_platforms:
raise ValueError(f'Invalid platform. Must be one of: {", ".join(valid_platforms)}')
deleted_count = 0
page = 1
while True:
# Fetch scheduled posts for this platform
response = requests.get(
f'{BASE_URL}/list-posts',
headers={'x-api-key': PUBLORA_API_KEY},
params={
'status': 'scheduled',
'platform': platform,
'page': page,
'limit': 100
}
)
data = response.json()
posts = data.get('posts', [])
if not posts:
break
# Delete each post
for post in posts:
try:
delete_scheduled_post(post['postGroupId'])
deleted_count += 1
print(f"Deleted: {post['postGroupId']}")
except PostPostError as e:
print(f"Failed to delete {post['postGroupId']}: {e}")
# Check for more pages
pagination = data.get('pagination', {})
if not pagination.get('hasNextPage'):
break
page += 1
return deleted_count
# ============ USAGE EXAMPLES ============
def delete_single_post_example():
"""Delete a single post."""
try:
result = delete_scheduled_post('507f1f77bcf86cd799439011')
print('Post deleted successfully')
return result
except PostPostError as e:
if e.status_code == 404:
print('Post already deleted or not found')
else:
print(f'Failed to delete: {e}')
raise
def delete_batch_example():
"""Delete multiple posts with summary."""
post_ids = [
'507f1f77bcf86cd799439011',
'507f1f77bcf86cd799439012',
'507f1f77bcf86cd799439013'
]
results = delete_multiple_posts(post_ids)
print(f'Succeeded: {len(results.succeeded)}')
print(f'Failed: {len(results.failed)}')
if results.failed:
print('Failed deletions:')
for f in results.failed:
print(f" {f['postGroupId']}: {f['error']}")
return results
def delete_with_confirmation_example(post_group_id: str):
"""Delete with confirmation - fetch details first."""
# First, get post details
response = requests.get(
f'{BASE_URL}/get-post/{post_group_id}',
headers={'x-api-key': PUBLORA_API_KEY}
)
if not response.ok:
raise PostPostError('Post not found', response.status_code)
post_data = response.json()
platforms = ', '.join(p['platform'] for p in post_data.get('posts', []))
content = post_data['posts'][0].get('content', '')[:50] if post_data.get('posts') else ''
print('About to delete:')
print(f' Content: {content}...')
print(f' Platforms: {platforms}')
print(f" Status: {post_data['posts'][0].get('status') if post_data.get('posts') else 'unknown'}")
# Perform deletion
return delete_scheduled_post(post_group_id)
# Run examples
if __name__ == '__main__':
# Delete single post
result = delete_single_post_example()
# Or delete all scheduled Twitter posts
# count = delete_all_scheduled_for_platform('twitter')
# print(f'Deleted {count} Twitter posts')cURL Examples
Delete a single post
curl -X DELETE "https://api.postpost.dev/api/v1/delete-post/507f1f77bcf86cd799439011" \
-H "x-api-key: YOUR_API_KEY"
# Response:
# { "success": true }Bulk delete script
#!/bin/bash
API_KEY="YOUR_API_KEY"
POST_IDS=("507f1f77bcf86cd799439011" "507f1f77bcf86cd799439012" "507f1f77bcf86cd799439013")
SUCCEEDED=0
FAILED=0
for POST_ID in "${POST_IDS[@]}"; do
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
"https://api.postpost.dev/api/v1/delete-post/${POST_ID}" \
-H "x-api-key: ${API_KEY}")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" = "200" ]; then
echo "Deleted $POST_ID successfully"
SUCCEEDED=$((SUCCEEDED + 1))
else
echo "Failed to delete $POST_ID: $BODY"
FAILED=$((FAILED + 1))
fi
done
echo ""
echo "Summary: $SUCCEEDED succeeded, $FAILED failed"Deletion Restrictions
| Post Status | Can Delete? | Notes |
|---|---|---|
draft | Yes | Not yet scheduled |
scheduled | Yes | Before publication time |
pending | No | Being processed by scheduler |
processing | No | Currently publishing |
published | No | Already live on platforms |
failed | Yes | Clean up failed attempts |
Error Reference
| Status | Error | Cause | Solution |
|---|---|---|---|
| 401 | "Invalid API key" | Bad or missing x-api-key | Check API key |
| 404 | "Post group not found" | ID doesn't exist or wrong user | Verify postGroupId |
| 500 | "Failed to delete post group" | Database transaction failed | Retry the request |
Important Notes
Atomic Deletion
The deletion uses a MongoDB transaction ensuring all database records (post group, platform posts, media records) are deleted atomically. If any database deletion fails, the entire operation is rolled back.
Media Cleanup
Media files are deleted from S3 after the database transaction commits. S3 deletion failures are logged but do not fail the request—your post will still be successfully deleted.
Rate Limiting
For bulk deletions, add a small delay between requests to avoid rate limits:
for (const postId of postIds) {
await deleteScheduledPost(postId);
await new Promise(r => setTimeout(r, 100)); // 100ms delay
}Idempotent Behavior
Deleting an already-deleted post returns a 404 error. Check for this status code to handle idempotent deletion gracefully:
try {
await deleteScheduledPost(postGroupId);
} catch (error) {
if (error.statusCode === 404) {
// Already deleted, treat as success
console.log('Post was already deleted');
} else {
throw error;
}
}Related Guides
- Create Post - Create new posts
- Get Post - Check post status before deletion
- Update Scheduled Post - Reschedule instead of delete
- List Posts - Find posts to delete
Update Scheduled Post
Update the timing or status of an already-scheduled post using the PostPost API. This guide covers the complete workflow with error handling and edge cases.
Workspace / B2B
This guide covers how to use the PostPost Workspace API to manage multiple users under your account. This is designed for B2B integrations where you need to create and manage social media posting on b