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

  1. Install requests (pip install requests) and put the API key in the environment variable PIXPLACE_API_KEY.
  2. Wrap every call in one function that adds the Authorization header, an optional Idempotency-Key and waits for Retry-After on 429 and 503.
  3. Upload the image with POST /api/v1/uploads/from-url, then create the post with POST /api/v1/posts using data.publicUrl.
  4. Derive the Idempotency-Key from the date of the run: running the script twice on one day cannot publish twice.
  5. 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.py

Common errors

StatusCauseWhat to do
400The 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.
401The 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.
403The 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.
429A 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.
502The 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.

Other tools