Skip to content

Publish and schedule posts with the Python SDK ​

client.uploads creates posts, checks their status, and retries or publishes them. All arguments are keyword-only, and every method returns a dict.

python
from socifyr import Socifyr

client = Socifyr()

How do I publish a text post from Python? ​

python
ack = client.uploads.text(
    connections=["x-myhandle", "linkedin-mybrand"],
    text="Shipped v2 today.",
)
print(ack["requestId"], ack["totalChannels"])

Signature:

python
text(*, connections: list[str], text: str, scheduled_at: str | None = None,
     timezone: str | None = None, first_comment: str | dict | None = None,
     save_draft: bool | None = None) -> dict

connections are slugs such as x-myhandle. See connection concepts. first_comment is a string or {"text": ..., "linkUrl": ..., "imageUrl": ...}.

What does a create call return? ​

Every create method (text, photos, video) sends async: true. That means the API queues the post and returns an acknowledgement instead of waiting:

python
{
    "success": True,
    "requestId": "9f2c7a1e-3d44-4b2a-8e1f-0a5c2d7e9f10",
    "usage": {"count": 4, "limit": 100, "remaining": 96},
    "message": "Upload processing started",
    "scheduledAt": None,
    "totalChannels": 2,
}

There is no content ID in this ack. Use requestId with get_status; the status response includes uploadId. For a scheduled post, message is "Upload scheduled" and scheduledAt is set. usage.limit of -1 means unlimited.

Two other ack shapes exist:

CaseAck
save_draft=True{"success": True, "draftId": "..."}
Key has requireApproval{"success": True, "uploadId": "...", "status": "pending_approval", "message": "..."}

How do I publish photos or a video? ​

photos and video take media by library ID or public URL. They cannot upload a file. See Media.

python
client.uploads.photos(
    connections=["instagram-mybrand"],
    medias=["6a04f715d3d7d93575e65d13", "https://cdn.example.com/b.jpg"],
    text="Two photos from the shoot",
)

client.uploads.video(
    connections=["youtube-mybrand", "tiktok-me"],
    medias=["6a04f715d3d7d93575e65d14"],
    text="Behind the scenes",
)

Both accept connections, medias, text and scheduled_at only. For titles, descriptions, platformOptions or documents, use the TypeScript SDK or the REST API.

How do I schedule a post with the Python SDK? ​

python
client.uploads.text(
    connections=["linkedin-mybrand"],
    text="Tomorrow at 9am",
    scheduled_at="2026-12-01T09:00:00Z",
    timezone="America/New_York",
)

scheduled_at is ISO-8601. timezone exists on text only. photos and video accept scheduled_at but not timezone.

How do I bulk schedule posts from a CSV? ​

python
res = client.uploads.bulk_schedule(rows=[
    {"text": "Morning", "connections": ["x-me"], "scheduledAt": "2026-12-01T09:00:00Z"},
    {"text": "Evening", "connections": ["x-me"], "scheduledAt": "2026-12-01T18:00:00Z"},
])
print(res["created"], res["failed"])

Or pass a CSV string with the header text,connections,scheduledAt,title:

python
with open("posts.csv", encoding="utf-8") as f:
    res = client.uploads.bulk_schedule(csv=f.read())

Returns {"created": int, "failed": [{"row": int, "error": str}]}. A bad row is reported in failed and does not stop the rest. Row keys are camelCase (scheduledAt). Text posts only.

How do I check a post's status? ​

python
status = client.uploads.get_status(ack["requestId"])
print(status["status"])  # pending, processing, publishing, completed, partial_failure, failed
for ch in status["channels"]:
    print(ch["platform"], ch["result"])

get_status also accepts a content ID. There is no wait helper, so poll in a loop:

python
import time

def wait(client, request_id, interval=3, timeout=300):
    deadline = time.time() + timeout
    while time.time() < deadline:
        s = client.uploads.get_status(request_id)
        if s["status"] in ("completed", "failed", "partial_failure"):
            return s
        time.sleep(interval)
    raise TimeoutError(request_id)

How do I list, get, retry or publish a draft? ​

python
page = client.uploads.list(page=1, limit=20, status="failed")
for item in page["data"]:
    print(item["id"], item["status"])

# "from" is a Python keyword, so pass date filters through a dict
recent = client.uploads.list(**{"from": "2026-11-01", "to": "2026-11-30"})

detail = client.uploads.get("6b1f0c2a9d3e4f5a6b7c8d9e")

client.uploads.retry("6b1f0c2a9d3e4f5a6b7c8d9e")        # re-queue failed channels
client.uploads.publish_draft("6b1f0c2a9d3e4f5a6b7c8d9e")  # publish a saved draft

list forwards any keyword arguments as query parameters (page, limit, status, platform, type, from, to). It returns {"data": [...], "meta": {"total", "page", "limit", "totalPages"}}. retry and publish_draft return the API ack.

What can I not do with the Python uploads resource? ​

No cancel, publish-now, update draft, archive, bulk delete, document posts, file upload, or approval calls. Use the TypeScript SDK or the REST API.

FAQ ​

Why is there no id in the ack? ​

The Python client forces async: true, and the async ack only carries requestId. Fetch the ID with get_status(...)["uploadId"].

Can I post to a slug that does not exist? ​

No. The API answers 400 and the SDK raises ValidationError. List valid slugs with list_connections.

Next steps ​