Integration recipe
Post to pixPLace from Python (requests) on a schedule
A Python script with requests that uploads media by URL, publishes a post, retries on 429 and 503, and runs from cron.
Who it is for
Developers and data people who already have content in Python and want it published on a schedule.
What it automates
Publish one post per run from a cron job or a task scheduler, with safe retries.
Steps
- Install requests (pip install requests) and put the API key in the environment variable PIXPLACE_API_KEY.
- Wrap every call in one function that adds the Authorization header, an optional Idempotency-Key and waits for Retry-After on 429 and 503.
- Upload the image with POST /api/v1/uploads/from-url, then create the post with POST /api/v1/posts using data.publicUrl.
- Derive the Idempotency-Key from the date of the run: running the script twice on one day cannot publish twice.
- When you create the key, tick the `publish` scope. A key without it gets 403 insufficient_scope on these calls.
Examples
Placeholders: ppk_live_XXXXXXXX is your key. The ids and the image address are samples. The API address is always https://pixplace.space/api/v1.
publish.py
import os
import time
import requests
BASE = "https://pixplace.space/api/v1"
API_KEY = os.environ["PIXPLACE_API_KEY"]
TODAY = time.strftime("%Y-%m-%d")
def call(method, path, body=None, key=None):
headers = {"Authorization": "Bearer " + API_KEY}
if key:
headers["Idempotency-Key"] = key
for attempt in range(4):
r = requests.request(method, BASE + path, json=body, headers=headers, timeout=60)
if r.status_code in (429, 503):
wait = int(r.headers.get("Retry-After", "5"))
if wait > 120: # a daily limit: do not sleep for hours, stop and alert
raise RuntimeError("limit reached, retry in %d s" % wait)
time.sleep(wait)
continue
r.raise_for_status()
return r.json()
raise RuntimeError("still limited after 4 tries")
upload = call("POST", "/uploads/from-url", {"url": "https://example.com/photos/living-room.jpg"}, "upload-" + TODAY)["data"]
post = call(
"POST",
"/posts",
{
"title": "Minimalist living room",
"attachments": [{"url": upload["publicUrl"], "type": upload["type"]}],
"description": "AI-generated interior concept",
},
"post-" + TODAY,
)
print(post["data"]["url"])Run it every day at 09:00 (crontab -e)
0 9 * * * PIXPLACE_API_KEY=ppk_live_XXXXXXXX /usr/bin/python3 /home/me/publish.pyCommon errors
| Status | Cause | What to do |
|---|---|---|
| 400 | The body is invalid: a missing title, a media URL that is not https, or a type other than IMAGE or VIDEO. The message names the field. | Fix the field named in the error message. Nothing was written and no quota was spent, so do not retry the same body. |
| 401 | The API key is missing, malformed, revoked or sent in the wrong header. | requests sends the header you give it: check that the key is not an empty string because the variable is not set. The header must read Authorization: Bearer followed by your ppk_live_ key. Check the key in Settings → Developer. |
| 403 | The key lacks a scope the call needs: the answer is 403 with error.code insufficient_scope and the missing scope in error.required. | Create a new key in Settings → Developer with the `publish` scope ticked and use it instead; repeating the call with the same key fails the same way. |
| 429 | A per-minute or per-day limit was reached. The Retry-After header and the retryAfter field of the body say how many seconds to wait. | Wait that long and repeat with the SAME Idempotency-Key. The script above sleeps for a short Retry-After and stops on a long one, which is a daily limit. |
| 502 | The server could not download the source URL: it answered 4xx or 5xx, the host does not resolve or the response was compressed. | Open the source URL in a private browser window. It must be public and answer 200 without a login. |
The full list of statuses, the retry rules and the limits are in Errors and retries.