Python: Bulk Import from CSV
Schedule multiple posts from a CSV file using Python.
CSV Format
Create a CSV file (posts.csv) with your content:
content,platforms,scheduled_time,media_url
"Monday motivation: Start strong!",twitter-123456;linkedin-ABC123,<FUTURE_ISO_8601_UTC>,
"Check out our new feature!",twitter-123456,<FUTURE_ISO_8601_UTC>,https://example.com/feature.png
"Weekly roundup thread",twitter-123456,<FUTURE_ISO_8601_UTC>,
"LinkedIn deep dive post",linkedin-ABC123,<FUTURE_ISO_8601_UTC>,https://example.com/chart.pngReplace each <FUTURE_ISO_8601_UTC> with the intended future UTC time before importing.
Basic CSV Import
import csv
import requests
from datetime import datetime
PUBLORA_API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://api.publora.com/api/v1'
headers = {
'Content-Type': 'application/json',
'x-publora-key': PUBLORA_API_KEY
}
def import_from_csv(csv_file):
results = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
# Parse platforms (semicolon-separated)
platforms = row['platforms'].split(';')
# Build post payload
payload = {
'content': row['content'],
'platforms': platforms,
'scheduledTime': row['scheduled_time']
}
if row.get('media_url'):
payload['mediaUrls'] = [row['media_url']]
# Create the post
response = requests.post(
f'{BASE_URL}/create-post',
headers=headers,
json=payload
)
result = {
'content': row['content'][:50] + '...',
'scheduled': row['scheduled_time'],
'success': response.ok
}
if response.ok:
result['postGroupId'] = response.json()['postGroupId']
else:
result['error'] = response.json().get('error', 'Unknown error')
results.append(result)
print(f"{'✓' if response.ok else '✗'} {result['content']}")
# Summary
successful = sum(1 for r in results if r['success'])
print(f"\nImported {successful}/{len(results)} posts")
return results
# Usage
results = import_from_csv('posts.csv')Advanced CSV Import with Validation
import csv
import requests
from datetime import datetime, timezone
import time
PUBLORA_API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://api.publora.com/api/v1'
headers = {
'Content-Type': 'application/json',
'x-publora-key': PUBLORA_API_KEY
}
def validate_row(row, row_num):
"""Validate a CSV row before processing."""
errors = []
if not row.get('content'):
errors.append(f"Row {row_num}: Missing content")
if not row.get('platforms'):
errors.append(f"Row {row_num}: Missing platforms")
if row.get('scheduled_time'):
try:
dt = datetime.fromisoformat(row['scheduled_time'].replace('Z', '+00:00'))
if dt < datetime.now(timezone.utc):
errors.append(f"Row {row_num}: Scheduled time is in the past")
except ValueError:
errors.append(f"Row {row_num}: Invalid date format (use ISO 8601)")
return errors
def import_csv_advanced(csv_file, dry_run=False, delay_ms=500):
"""
Import posts from CSV with validation and rate limiting.
Args:
csv_file: Path to CSV file
dry_run: If True, validate only without posting
delay_ms: Delay between API calls in milliseconds
"""
all_errors = []
results = []
# First pass: validate all rows
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
rows = list(reader)
print(f"Validating {len(rows)} rows...")
for i, row in enumerate(rows, 1):
errors = validate_row(row, i)
all_errors.extend(errors)
if all_errors:
print("\nValidation errors:")
for error in all_errors:
print(f" - {error}")
return {'success': False, 'errors': all_errors}
print("Validation passed!")
if dry_run:
print("\nDry run mode - no posts created")
return {'success': True, 'dry_run': True, 'row_count': len(rows)}
# Second pass: create posts
print(f"\nCreating {len(rows)} posts...")
for i, row in enumerate(rows, 1):
platforms = row['platforms'].split(';')
payload = {
'content': row['content'],
'platforms': platforms
}
if row.get('scheduled_time'):
payload['scheduledTime'] = row['scheduled_time']
if row.get('media_url'):
payload['mediaUrls'] = [row['media_url']]
response = requests.post(
f'{BASE_URL}/create-post',
headers=headers,
json=payload
)
result = {
'row': i,
'success': response.ok,
'content_preview': row['content'][:40]
}
if response.ok:
result['postGroupId'] = response.json()['postGroupId']
print(f" [{i}/{len(rows)}] ✓ Created: {result['postGroupId']}")
else:
result['error'] = response.json().get('error', 'Unknown error')
print(f" [{i}/{len(rows)}] ✗ Failed: {result['error']}")
results.append(result)
# Rate limiting
if i < len(rows):
time.sleep(delay_ms / 1000)
# Summary
successful = sum(1 for r in results if r['success'])
failed = len(results) - successful
print(f"\n{'='*40}")
print(f"Import complete: {successful} succeeded, {failed} failed")
return {
'success': failed == 0,
'total': len(results),
'successful': successful,
'failed': failed,
'results': results
}
# Usage
# Validate only (dry run)
import_csv_advanced('posts.csv', dry_run=True)
# Actually import
import_csv_advanced('posts.csv', delay_ms=1000)Generate CSV Template
def generate_csv_template(output_file='posts_template.csv'):
"""Generate a sample CSV template."""
sample_rows = [
{
'content': 'Monday motivation: Start your week strong!',
'platforms': 'twitter-123456;linkedin-ABC123',
'scheduled_time': '<FUTURE_ISO_8601_UTC>',
'media_url': ''
},
{
'content': 'Check out our latest blog post!',
'platforms': 'twitter-123456',
'scheduled_time': '<FUTURE_ISO_8601_UTC>',
'media_url': 'https://example.com/blog-image.png'
},
{
'content': 'Behind the scenes at our office',
'platforms': 'instagram-789012',
'scheduled_time': '<FUTURE_ISO_8601_UTC>',
'media_url': 'https://example.com/office-photo.jpg'
}
]
with open(output_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['content', 'platforms', 'scheduled_time', 'media_url'])
writer.writeheader()
writer.writerows(sample_rows)
print(f"Template created: {output_file}")
generate_csv_template()Export Scheduled Posts to CSV
def export_scheduled_to_csv(output_file='scheduled_posts.csv'):
"""Export all scheduled posts to CSV for backup/review."""
# Get all scheduled posts (you'd need to implement pagination for large sets)
response = requests.get(
f'{BASE_URL}/platform-connections',
headers={'x-publora-key': PUBLORA_API_KEY}
)
# This is a simplified example - actual implementation would
# fetch posts differently based on your needs
posts = [] # Fetch your scheduled posts here
with open(output_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=[
'postGroupId', 'content', 'platforms', 'scheduled_time', 'status'
])
writer.writeheader()
for post in posts:
writer.writerow({
'postGroupId': post['postGroupId'],
'content': post['content'],
'platforms': ';'.join(post['platforms']),
'scheduled_time': post['scheduledTime'],
'status': post['status']
})
print(f"Exported {len(posts)} posts to {output_file}")Using pandas for Larger Datasets
import pandas as pd
import requests
from datetime import datetime
import time
PUBLORA_API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://api.publora.com/api/v1'
headers = {
'Content-Type': 'application/json',
'x-publora-key': PUBLORA_API_KEY
}
def import_with_pandas(csv_file):
"""Import posts using pandas for better data handling."""
df = pd.read_csv(csv_file)
# Clean up data
df['content'] = df['content'].str.strip()
df['platforms'] = df['platforms'].str.split(';')
df['scheduled_time'] = pd.to_datetime(df['scheduled_time'])
# Filter out past dates
now = datetime.now()
df = df[df['scheduled_time'] > now]
print(f"Processing {len(df)} future posts...")
results = []
for idx, row in df.iterrows():
payload = {
'content': row['content'],
'platforms': row['platforms'],
'scheduledTime': row['scheduled_time'].isoformat() + 'Z'
}
response = requests.post(
f'{BASE_URL}/create-post',
headers=headers,
json=payload
)
results.append({
'index': idx,
'success': response.ok,
'postGroupId': response.json().get('postGroupId') if response.ok else None
})
time.sleep(0.5) # Rate limiting
# Add results back to dataframe
results_df = pd.DataFrame(results)
df = df.reset_index(drop=True)
df['import_success'] = results_df['success']
df['postGroupId'] = results_df['postGroupId']
# Save results
df.to_csv('import_results.csv', index=False)
success_count = df['import_success'].sum()
print(f"Imported {success_count}/{len(df)} posts")
return df
# Usage
df = import_with_pandas('posts.csv')
print(df[['content', 'import_success', 'postGroupId']])Publora — Social media API with free tier, paid plans from $2.99/account