Skip to content

Python Examples

These examples demonstrate how to use the Tailwind API with Python.

Replace the example account, board, post, media, and destination values with your own. Scheduling examples use a date in 2099 so they remain valid; choose the future date and time you actually want.

Terminal window
pip install requests
import os
import requests
from datetime import datetime, timezone
API_URL = 'https://api-v1.tailwind.ai'
API_KEY = os.environ['TAILWIND_API_KEY']
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
def list_accounts():
response = requests.get(
f'{API_URL}/v1/accounts',
headers=headers
)
response.raise_for_status()
return response.json()['data']['accounts']
# Usage
accounts = list_accounts()
for account in accounts:
print(f"Account: {account['displayName']} ({account['id']})")
def list_boards(account_id: str):
response = requests.get(
f'{API_URL}/v1/accounts/{account_id}/boards',
headers=headers
)
response.raise_for_status()
return response.json()['data']['boards']
# Usage
boards = list_boards('123456')
for board in boards:
print(f"Board: {board['name']} ({board['id']})")
def create_post(account_id: str, post_data: dict):
response = requests.post(
f'{API_URL}/v1/accounts/{account_id}/posts',
headers=headers,
json=post_data
)
response.raise_for_status()
return response.json()['data']['post']
# Usage - Create a scheduled post
post = create_post('123456', {
'mediaUrl': 'https://example.com/my-image.jpg',
'title': 'Amazing Recipe',
'description': 'Try this delicious recipe! #recipes #cooking',
'url': 'https://myblog.com/recipe',
'boardId': '1106196864631757445',
'altText': 'A colorful plate of food',
'sendAt': '2099-01-20T14:00:00Z'
})
print(f"Created post: {post['id']}")
print(f"Status: {post['status']}")
# Set mediaType to 'video' for video pins
video_post = create_post('123456', {
'mediaUrl': 'https://example.com/my-video.mp4',
'mediaType': 'video',
'title': 'Watch This Tutorial',
'description': 'Step-by-step guide #tutorial #video',
'url': 'https://myblog.com/tutorial',
'boardId': '1106196864631757445',
'sendAt': '2099-01-20T14:00:00Z'
})
print(f"Video post created: {video_post['id']}")
# Omit sendAt to create a draft
draft = create_post('123456', {
'mediaUrl': 'https://example.com/my-image.jpg',
'title': 'Draft for Review',
'description': 'I will schedule this later',
'boardId': '1106196864631757445'
})
print(f"Draft created: {draft['id']}")
# draft['status'] will be 'draft'

If the draft doesn’t already have a boardId, provide one when scheduling:

def schedule_post(account_id: str, post_id: str, send_at: str, board_id: str = None):
body = {'sendAt': send_at}
if board_id:
body['boardId'] = board_id
response = requests.post(
f'{API_URL}/v1/accounts/{account_id}/posts/{post_id}/schedule',
headers=headers,
json=body
)
response.raise_for_status()
return response.json()['data']['post']
# Usage
scheduled = schedule_post(
'123456',
'post_abc123',
'2099-01-25T09:00:00Z',
'1106196864631757445' # Required if draft has no board assigned
)
print(f"Scheduled for: {datetime.fromtimestamp(scheduled['sendAt'], tz=timezone.utc)}")
def list_posts(account_id: str, **kwargs):
params = {}
if 'status' in kwargs:
params['status'] = kwargs['status']
if 'limit' in kwargs:
params['limit'] = kwargs['limit']
if 'cursor' in kwargs:
params['cursor'] = kwargs['cursor']
if 'start_date' in kwargs:
params['startDate'] = kwargs['start_date']
if 'end_date' in kwargs:
params['endDate'] = kwargs['end_date']
response = requests.get(
f'{API_URL}/v1/accounts/{account_id}/posts',
headers=headers,
params=params
)
response.raise_for_status()
data = response.json()['data']
return {
'posts': data['posts'],
'cursor': data.get('cursor')
}
# List queued posts (default)
queued = list_posts('123456')
print(f"Queued posts: {len(queued['posts'])}")
# List drafts
drafts = list_posts('123456', status='draft')
print(f"Drafts: {len(drafts['posts'])}")
# List sent posts in a date range
sent = list_posts(
'123456',
status='sent',
start_date='2024-01-01T00:00:00Z',
end_date='2024-01-31T23:59:59Z',
limit=100
)
print(f"Sent in January: {len(sent['posts'])}")
def delete_post(account_id: str, post_id: str):
response = requests.delete(
f'{API_URL}/v1/accounts/{account_id}/posts/{post_id}',
headers=headers
)
response.raise_for_status()
return True
# Usage
delete_post('123456', 'post_abc123')
print('Post deleted')
def bulk_schedule(account_id: str, posts: list):
"""Schedule multiple posts with a delay between each."""
results = []
for post_data in posts:
try:
post = create_post(account_id, post_data)
results.append({'success': True, 'post': post})
print(f"Scheduled: {post['id']}")
except Exception as e:
results.append({
'success': False,
'error': str(e),
'data': post_data
})
print(f"Failed: {e}")
return results
# Usage
posts_to_schedule = [
{
'mediaUrl': 'https://example.com/image1.jpg',
'title': 'Post 1',
'description': 'First post',
'url': 'https://example.com/post-1',
'boardId': '1106196864631757445',
'sendAt': '2099-01-20T09:00:00Z'
},
{
'mediaUrl': 'https://example.com/image2.jpg',
'title': 'Post 2',
'description': 'Second post',
'url': 'https://example.com/post-2',
'boardId': '1106196864631757445',
'sendAt': '2099-01-20T14:00:00Z'
},
{
'mediaUrl': 'https://example.com/image3.jpg',
'title': 'Post 3',
'description': 'Third post',
'url': 'https://example.com/post-3',
'boardId': '1106196864631757445',
'sendAt': '2099-01-20T19:00:00Z'
}
]
results = bulk_schedule('123456', posts_to_schedule)
successful = sum(1 for r in results if r['success'])
print(f"Scheduled {successful} posts")
from dataclasses import dataclass
from typing import Optional, List
from datetime import datetime
@dataclass
class Account:
id: str
user_id: Optional[str]
display_name: Optional[str]
username: Optional[str]
avatar_url: Optional[str]
token_authorized: bool
is_domain_verified: bool
created_at: Optional[int]
@dataclass
class Board:
id: str
name: str
is_collaborator: bool
is_secret: bool
@dataclass
class Post:
id: str
status: str # 'draft' | 'queued' | 'sent' | 'uploading'
media_url: str
media_type: str # 'image' | 'video'
title: Optional[str]
description: Optional[str]
url: Optional[str]
board_id: Optional[str]
alt_text: Optional[str]
send_at: Optional[int]
sent_at: Optional[int]
created_at: int
pin_id: Optional[str]
is_simplified_pin: bool
def parse_account(data: dict) -> Account:
return Account(
id=data['id'],
user_id=data.get('userId'),
display_name=data['displayName'],
username=data['username'],
avatar_url=data.get('avatarUrl'),
token_authorized=data['tokenAuthorized'],
is_domain_verified=data['isDomainVerified'],
created_at=data.get('createdAt')
)
def parse_post(data: dict) -> Post:
return Post(
id=data['id'],
status=data['status'],
media_url=data['mediaUrl'],
media_type=data['mediaType'],
title=data.get('title'),
description=data.get('description'),
url=data.get('url'),
board_id=data.get('boardId'),
alt_text=data.get('altText'),
send_at=data.get('sendAt'),
sent_at=data.get('sentAt'),
created_at=data['createdAt'],
pin_id=data.get('pinId'),
is_simplified_pin=data['isSimplifiedPin']
)
from requests.exceptions import HTTPError
def safe_api_call(func):
"""Decorator for handling API errors."""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except HTTPError as e:
if e.response.status_code == 401:
print('Authentication failed. Check your API key.')
elif e.response.status_code == 429:
print('Daily rate limit exceeded. Retry after midnight UTC.')
elif e.response.status_code == 404:
print('Resource not found. Check your IDs.')
else:
print(f'API error: {e}')
raise
return wrapper
@safe_api_call
def get_accounts():
return list_accounts()
# Usage
accounts = get_accounts()
import asyncio
import aiohttp
async def list_accounts_async():
async with aiohttp.ClientSession() as session:
async with session.get(
f'{API_URL}/v1/accounts',
headers=headers
) as response:
data = await response.json()
return data['data']['accounts']
async def create_posts_async(account_id: str, posts: list):
"""Create multiple posts concurrently."""
async with aiohttp.ClientSession() as session:
tasks = []
for post_data in posts:
task = session.post(
f'{API_URL}/v1/accounts/{account_id}/posts',
headers=headers,
json=post_data
)
tasks.append(task)
responses = await asyncio.gather(*tasks, return_exceptions=True)
results = []
for i, response in enumerate(responses):
if isinstance(response, Exception):
results.append({'success': False, 'error': str(response)})
else:
data = await response.json()
results.append({'success': True, 'post': data['data']['post']})
return results
# Usage
accounts = asyncio.run(list_accounts_async())