How to Automate Cross-Posting with Python

12 min read Python developers building social automationUpdated 2026-05-06

Cross-posting from Python usually means writing nine separate API integrations, each with its own auth flow, rate limits, and quirks. CodivUpload's Python SDK collapses that into one method call: client.posts.create(...). You connect each social account once inside a CodivUpload profile, then reference the profile by name and pass an array of platforms. The SDK handles authentication, retries on transient errors, and gives you typed responses you can introspect with your IDE's autocomplete. This guide shows the full installation, a real cross-posting script that targets five platforms in a single call, and the patterns for handling per-platform failures gracefully — including the partially_failed lifecycle state and the retry endpoint.

Prerequisites

  • Python 3.9 or newer
  • A CodivUpload API key from Dashboard → API Keys
  • A CodivUpload profile with at least one social account connected

Step-by-step

  1. 1

    Install the official Python SDK

    The codivupload package is published on PyPI. It's auto-generated from the OpenAPI spec, so every endpoint you see in api.codivupload.com is available as a typed method. The SDK has zero runtime dependencies beyond httpx and pydantic.

    pip install codivupload
  2. 2

    Initialize the client

    Pass your API key to the constructor. The SDK reads CODIVUPLOAD_API_KEY from the environment if no key is passed explicitly — preferred for production deployments where the key lives in your secret manager. The client is thread-safe and reusable; instantiate it once at process startup.

    from codivupload import CodivUpload
    
    # Explicit:
    client = CodivUpload(api_key="YOUR_API_KEY")
    
    # Or via env var (recommended for production):
    # export CODIVUPLOAD_API_KEY=...
    client = CodivUpload()
  3. 3

    Cross-post to multiple platforms in a single call

    Pass profile_name and a platforms array. CodivUpload publishes to each platform in parallel and returns a post_id you can poll. Per-platform overrides let you customize the post for each network without making separate calls — for example a longer description on LinkedIn, a different cover image on Instagram, and reply controls on X.

    post = client.posts.create(
        post_type="image",
        profile_name="my_brand",
        platforms=["instagram", "tiktok", "x", "linkedin", "facebook"],
        media_urls=["https://your-cdn.example.com/cover.jpg"],
        title="Product Launch — May 2026",
        description="Our biggest update yet. Read the full breakdown on the blog.",
        instagram_share_to_feed=True,
        instagram_alt_text="Product shot on a beige backdrop",
        x_reply_settings="following",
        linkedin_visibility="PUBLIC",
    )
    print(post.post_id, "→", post.destinations_count, "destinations")
  4. 4

    Schedule for later instead of posting now

    Add scheduled_date as an ISO 8601 UTC string. CodivUpload queues the post inside pgmq and the worker fires at the exact timestamp. You can also use schedule_best_time=True (mutually exclusive with scheduled_date) to let CodivUpload pick the optimal slot based on the connected accounts' last 90 days of analytics.

    from datetime import datetime, timedelta, timezone
    
    # Post tomorrow at 14:00 UTC
    when = (datetime.now(timezone.utc) + timedelta(days=1)).replace(hour=14, minute=0, second=0)
    post = client.posts.create(
        post_type="video",
        profile_name="my_brand",
        platforms=["youtube", "tiktok", "instagram"],
        media_urls=["https://your-cdn.example.com/clip.mp4"],
        title="Tomorrow's tip",
        description="Save this for later — it's how I batch a week of content in 90 minutes.",
        scheduled_date=when.isoformat(timespec="seconds"),
        youtube_type="shorts",
    )
  5. 5

    Poll for completion (or use webhooks)

    Posts go through scheduled → publishing → completed. Use the get method to poll, or — preferred for production — subscribe to the post.completed webhook event via /v1/webhooks. Webhooks fire on completed, partially_failed, and failed states with the full destinations payload included.

    import time
    
    while True:
        p = client.posts.get(post.post_id)
        if p.post.status in ("completed", "partially_failed", "failed"):
            break
        time.sleep(5)
    
    for d in p.destinations:
        if d.status == "success":
            print(f"✓ {d.platform}: {d.post_url}")
        else:
            print(f"✗ {d.platform}: {d.error_message}")
  6. 6

    Handle partial failures

    When some platforms succeed and others fail, the parent post status becomes partially_failed. Inspect destinations[i].error_message to triage — common causes are platform-specific (TikTok rate limit, Instagram media format mismatch, YouTube quota exhausted). After fixing the underlying issue, retry only the failed destinations with client.posts.retry_failed(post_id), which re-queues them without reposting the already-successful ones.

Frequently asked

Is the SDK async?+

The current SDK is synchronous (uses httpx synchronously). For async use, either run blocking calls in run_in_executor with FastAPI/Starlette, or use the REST API directly with httpx.AsyncClient. The async-native SDK is on the roadmap for v2.

How do I avoid duplicate posts if my script crashes mid-loop?+

Pass an idempotency_key (any unique string up to 64 chars). CodivUpload deduplicates POST /v1/posts requests by key for 24 hours — re-running the same call with the same key returns the original post_id without creating a new one.

Can the SDK upload local files?+

No — POST /v1/posts requires a public HTTPS URL. To upload a local file, first call /v1/upload-media to get a presigned R2 URL, then PUT the file to that URL, then pass the resulting CDN URL to /v1/posts. The SDK has a helper: client.upload_media('./video.mp4') returns the CDN URL.

What happens if I pass a platform the profile isn't connected to?+

The request returns 400 Bad Request with an error message naming the missing platform. CodivUpload validates the platforms array against the profile's connected accounts before queuing. This is intentional — silently skipping platforms is a worse failure mode than rejecting the call.

Does the SDK work in serverless (AWS Lambda, Vercel Functions, Cloud Run)?+

Yes. The client has no persistent state and the SDK package is under 1 MB total. Initialize the client at module level so it's reused across invocations — re-creating it per request adds ~50 ms of httpx connection setup.

How do I rotate my API key without downtime?+

Generate the new key first, deploy it as a second env var (CODIVUPLOAD_API_KEY_NEW), do a rolling restart of your workers so they pick it up, then revoke the old key from Dashboard → API Keys. CodivUpload doesn't impose a rotation deadline but rotating quarterly is good hygiene.

Is there a Node/TypeScript equivalent?+

Yes — the codivupload npm package mirrors the Python SDK. Same method shapes, same field names. Both are auto-generated from the OpenAPI spec, so they stay in sync as new endpoints ship.

Related guides

Ready to automate?

Free plan includes 30 posts/month across 11 platforms. No credit card required.

See pricing