We're building a self-hosted Bluesky client using Postiz, a lightweight, self-hostable social media platform, and driving it from a Python script. Our stack runs entirely in Docker Compose, and we've had to handle some quirks in the Postiz API, especially around the shape of the response when creating a post.
Postiz is a great choice for self-hosting because it's minimal and fast, but it's not without its gotchas. One of the first things we noticed was that the /api/public/v1/posts endpoint returns either a single object or a list depending on the context. This inconsistency required some careful handling on our end.
To get started, we set up Postiz in Docker Compose with a few custom configurations. Here's a simplified version of our docker-compose.yml:
version: '3.8'
services:
postiz:
image: postiz/postiz:latest
ports:
- "8080:8080"
environment:
POSTIZ_ADMIN_PASSWORD: "your-admin-password"
POSTIZ_PUBLIC_URL: "http://localhost:8080"
volumes:
- ./data:/var/lib/postiz
Once Postiz was running, we built a Python client to interact with its API. The core of the client is a function that sends a POST request to /api/public/v1/posts with the correct authentication header and JSON payload.
Here's the structure of the JSON payload we use:
{
"text": "Hello, Bluesky!",
"createdAt": "2023-10-05T12:34:56Z"
}
And the authentication header is constructed using the admin password we set in the Docker Compose file:
import requests
import json
from datetime import datetime, timezone
def create_post(text):
url = "http://localhost:8080/api/public/v1/posts"
headers = {
"Authorization": "Bearer your-admin-password",
"Content-Type": "application/json"
}
payload = {
"text": text,
"createdAt": datetime.now(timezone.utc).isoformat()
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
return response.json()
One of the more frustrating aspects of working with the Postiz API is that it sometimes returns a single object and other times a list. For example, when we make a POST request to create a post, the response might be a single object with the new post's details, but when we query all posts, it might return a list of objects.
To handle this, we added a helper function that checks the type of the response and normalizes it accordingly:
def normalize_response(response_data):
if isinstance(response_data, list):
return response_data
elif isinstance(response_data, dict):
return [response_data]
else:
return []
This normalization step ensures that our code can consistently handle both single objects and lists without having to write separate logic for each case.
We're currently working on extending this client to support more features like replies, likes, and user management. We're also exploring ways to make the client more robust by adding retries and better error handling for network issues.
What do you think about using Postiz for self-hosted social media? Have you encountered similar quirks in other APIs?
Top comments (0)