How to post to social media with Python

To post to social media with Python, send one requests.post to https://postwire.io/api/post with your PostWire key and a list of networks: {"platforms": ["linkedin", "bluesky"], "text": "…"}. PostWire publishes to each network through its official API and answers with one result per network: ok, the post's url, or an error with a code your script can branch on. Nothing to install beyond requests, and the free plan covers it: 1 business, 20 posts a month, 5 AI drafts a day, 2 networks per post, no card.

Connect each account once in the dashboard (TikTok, Instagram, YouTube, LinkedIn, Facebook Pages, Bluesky, Mastodon, Telegram, Discord, and X on paid plans), and your script never handles a network's tokens, app review or API changes.

Updated . Written from PostWire's own code; vendor prices read on Oct 15, 2026. Live networks: TikTok, Instagram, YouTube, LinkedIn (personal profiles), Facebook Pages, Bluesky, Mastodon, Telegram, Discord, and X on paid plans.

From zero to a published post, step by step

Set your key in the environment first: export POSTWIRE_API_KEY=pw_live_…. Every example below reads it from there.

  1. Create a free account and copy your key

    Open the dashboard, sign in with your email and a 6-digit code (no password, no card), and copy your key under API & MCP. It starts with pw_live_ and goes in the Authorization: Bearer header.

  2. Connect the accounts you post to

    Under Accounts, press Connect on each network; TikTok, Instagram, YouTube, LinkedIn and Facebook sign in on their own page, Telegram, Discord, Bluesky and Mastodon take a token or webhook. Your script can check them with GET /api/me.

  3. Publish a post

    One request, one result per network. The idempotency_key makes a retry safe: send the same key again after a timeout and the post is not published twice.

    import os, uuid, requests
    
    API = "https://postwire.io/api"
    HEAD = {"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"}
    key = str(uuid.uuid4())  # keep it for retries of this same post
    
    r = requests.post(f"{API}/post", headers=HEAD, timeout=120, json={
        "platforms": ["linkedin", "bluesky"],
        "text": "We cut our CI time from 9 minutes to 2. Here is what changed.",
        "idempotency_key": key,
    })
    for res in r.json().get("results", []):
        print(res["platform"], res.get("url") if res["ok"] else (res.get("code"), res.get("error")))
  4. Let the API write each network's version

    POST /api/generate turns one idea into a post written for each network and publishes nothing. Pass its drafts straight back as per_platform.

    drafts = requests.post(f"{API}/generate", headers=HEAD, timeout=120, json={
        "prompt": "Our new pricing page is live: plans from $9, a free plan with no card",
        "platforms": ["linkedin", "bluesky"],
    }).json()["drafts"]
    
    requests.post(f"{API}/post", headers=HEAD, timeout=120, json={
        "platforms": list(drafts), "per_platform": drafts,
    })
  5. Upload a video or photo from disk

    Three calls: ask for an upload URL, PUT the bytes there (they never pass through the API), and finalize to get a media_url. One PUT takes up to 50 MB; larger files are sent in parts with "multipart": true.

    path = "clip.mp4"
    up = requests.post(f"{API}/media/upload-url", headers=HEAD, json={
        "content_type": "video/mp4", "size_bytes": os.path.getsize(path),
    }).json()
    with open(path, "rb") as f:
        requests.put(up["upload_url"], data=f, headers={"Content-Type": "video/mp4"}).raise_for_status()
    media = requests.get(f"{API}/media/finalize", headers=HEAD, params={"path": up["path"]}).json()
    
    requests.post(f"{API}/post", headers=HEAD, timeout=300, json={
        "platforms": ["tiktok", "youtube"], "video_url": media["media_url"],
        "text": "CI in 2 minutes, start to finish",
        "per_platform": {"youtube": {"title": "CI in 2 minutes", "text": "How we did it"}},
    })
  6. Schedule instead of posting now

    POST /api/schedule takes the same body plus run_at: an ISO time up to 365 days ahead, or "next_slot" for the brand's next free posting hour. PostWire publishes it at that time; your script can exit.

    requests.post(f"{API}/schedule", headers=HEAD, json={
        "platforms": ["linkedin", "bluesky"],
        "text": "Office hours this Friday at 4pm.",
        "run_at": "2026-10-23T09:00:00-04:00",
    })

Every endpoint, field and error code: API reference and openapi.json. Prefer no code? The same calls run from the n8n node or from an AI agent through the MCP server.

Posting from Python: PostWire, Upload-Post and Ayrshare

What each needs in a Python project and what it costs to start, read on each vendor's own Python and pricing pages on Oct 15, 2026.

ServiceIn PythonAuth headerFree to startCheapest paid planWrites each network's text
PostWirerequests — no package to installAuthorization: Bearer pw_live_…Free plan: 1 business, 20 posts a month, 5 AI drafts a day, 2 networks per post, no card, TikTok included$9/mo — 3 businesses, every network in each postYes — POST /api/generate
Upload-Postpip install upload-post, or requestsAuthorization: Apikey …Free plan: 10 uploads a month, without TikTok$24/mo — 5 profilesNot listed
AyrshareThe Social-Post-API package on PyPINot listed28-day free trial of the Launch plan$149/mo billed monthly — Premium, 1 social profileNot listed

Checked on Oct 15, 2026 (2026-10-15) on each vendor's own pricing or docs page, linked in the first column and below. Monthly billing in USD unless noted. "Not listed" means we could not find it on their page, not that it is missing.

Where PostWire is ahead

  • A free plan with TikTok in it: 20 posts a month, no card and no time limit. Upload-Post's free plan has 10 uploads a month without TikTok; Ayrshare starts with a 28-day trial.
  • The cheapest paid plan here: $9 a month for 3 businesses. Upload-Post Basic is $24 and Ayrshare Premium $149, both billed monthly.
  • The API writes the posts too: POST /api/generate turns one idea into a LinkedIn post, an X post, a TikTok caption and a YouTube title, each written for its network, ready for per_platform.
  • Retries that never post twice: send the same idempotency_key after a timeout and the post goes out once.
  • Nothing to install or keep updated: three endpoints and a Bearer header, with the full openapi.json to generate a client if you want one.
Sources, all read on Oct 15, 2026:

Handling the answer in your script

  • Check each network, not only the status code: one request can publish on LinkedIn and fail on Bluesky. posted counts the networks that went out, and each item of results has ok.
  • Branch on code, not on the English message: not_connected (connect it in the dashboard), rate_limited with retry_after in seconds, networks_per_post with held: true (the free plan sends one post to 2 networks and holds the rest, spending nothing on them).
  • Several businesses: with the same network connected in more than one brand, pass brand_id (from GET /api/brands); without it the API answers 409 ambiguous_brand and publishes nothing.
  • Video can finish later: a result with status: "processing" is accepted; follow it with GET /api/post/status.

Source: PostWire's OpenAPI document and server code, read Oct 15, 2026.

What it does not do

PostWire has no Python package of its own: the REST API is small enough for requests (or httpx). It does not post to Threads, Reddit or Pinterest, to LinkedIn company pages or to personal Facebook profiles, and X is on paid plans only. Files sent by URL must be public https links.

Questions people ask

Can I post to Instagram and TikTok with Python?

Yes. Connect the accounts once in PostWire's dashboard, then send one requests.post to https://postwire.io/api/post with "platforms": ["instagram", "tiktok"] and a video_url or photo_url. PostWire publishes through Instagram's and TikTok's official APIs; Instagram needs a Business or Creator account.

Do I need a developer account for each network?

No. PostWire's own apps with TikTok, Meta, YouTube and LinkedIn do the publishing, so your script needs only a PostWire key. Telegram, Discord, Bluesky and Mastodon connect with a bot token, webhook or app password you paste in the dashboard.

Is there a PostWire Python SDK?

No, and none is needed: the REST API takes JSON and a Bearer header, so requests or httpx is enough. openapi.json describes every endpoint if you prefer to generate a typed client.

How do I schedule posts from Python?

POST to https://postwire.io/api/schedule with the same body as /api/post plus run_at, an ISO 8601 time up to 365 days ahead or "next_slot". PostWire stores the post and publishes it at that time, so the script can exit.

How do I upload a local video?

POST /api/media/upload-url with content_type and size_bytes, PUT the file to the upload_url it returns, then GET /api/media/finalize?path=… for a media_url, and pass it as video_url. One PUT takes up to 50 MB; larger files go in parts.

Is it free?

Yes, to start: 1 business, 20 posts a month, 5 AI drafts a day, 2 networks per post, no card, with the REST API included. Starter is $9 a month for 3 businesses and Pro $29 for 10, with every network in each post; paid plans have a 14-day refund.